Commit Graph
100 Commits
Author SHA1 Message Date
edde746 048fb77c8c fix(windows): move mpv teardown off platform thread 2026-07-12 08:42:19 +02:00
edde746 a63369c247 fix: close async lifecycle races 2026-07-12 08:42:19 +02:00
edde746 6d94880c2f fix(input): reset long press state safely 2026-07-12 08:42:19 +02:00
edde746 9c6fcbc841 fix(plex): tolerate scalar schema drift 2026-07-12 08:42:18 +02:00
edde746 a809cbbcef fix(plex): surface server failures 2026-07-12 08:42:18 +02:00
edde746 e4b0680158 fix(ci): enforce automation checks 2026-07-12 08:42:18 +02:00
edde746 64db84c3e2 feat(library): default clip libraries to folder grouping
close #966
2026-07-12 03:38:23 +02:00
edde746 4a81246986 build(libass): bump fork to 0.18.3 2026-07-12 00:44:08 +02:00
edde746 a23c7f215c fix(player): tighten staged back navigation 2026-07-11 23:11:25 +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 2679745941 fix: stop Jellyfin live TV sessions
close #1533
2026-07-11 22:00:05 +02:00
edde746 4443b76145 fix(player): respect visible chrome on back 2026-07-11 17:38:22 +02:00
edde746 3a982c2a7a fix(player): prevent duplicate back actions 2026-07-11 12:59:57 +02:00
edde746 e07172ade4 fix(tvos): enable native EAC3 JOC playback 2026-07-11 12:59:57 +02:00
edde746 d86e328169 fix(player): unify back navigation
close #1525
2026-07-11 03:23:40 +02:00
edde746 02a032d287 fix(android): reconnect Flutter texture after TV standby 2026-07-11 01:01:08 +02:00
edde746 03651d6c73 fix(exoplayer): unwrap AAC-LATM (LOAS) audio in MKV direct streams
close #1521
2026-07-10 23:23:44 +02:00
edde746 9b6c12d086 style: format playback_open 2026-07-10 21:26:25 +02:00
edde746 c5c7dd614f i18n: translate stream-interrupted snackbar to all locales 2026-07-10 20:27:52 +02:00
edde746 f87d6e5a52 fix(player): keep paused server sessions alive
Report the paused timeline every tick (~10s, matching official clients)
instead of every ~60s, and ping the Plex transcoder keepalive endpoint
alongside it while transcoding — PMS reaps idle transcode sessions that
timeline reports alone historically have not kept alive. Prevents the
transcode-variant of the #1520 stream death; the reporter's direct-play
case is covered by EOF classification and reconnect.
2026-07-10 19:12:36 +02:00
edde746 c885c0c6bc fix(player): intercept spurious mid-file EOF and recover in place
close #1520

Classify player EOF signals against the best-known duration: a mid-file
EOF means the stream died (transcode reaped or idle connection closed
during a long pause), not that the media ended — it must never mark the
item watched, prompt Play Next, or exit a movie. Recover with a bounded
in-place reload at the parked position; if the server is still refusing,
park on the old frame and rebuild the stream on user play/seek or when
the server-status monitor sees it come back online.
2026-07-10 19:11:18 +02:00
edde746 3bf72d7237 fix(player): auto-reconnect network streams on transient HTTP 503
ffmpeg's reconnect gave up on the first HTTP-error response, so a PMS
answering 503 (startup maintenance) after an idle-pause disconnect
surfaced as a clean mid-file EOF instead of riding through the outage.
2026-07-10 19:07:29 +02:00
edde746 d07d849fe4 refactor(player): return an outcome enum from _reloadMediaInPlace
The old bool meant "attempt was current and handled", so callers could
not distinguish a real pre-open failure (old stream still loaded) from
success — the distinction the #1520 recovery needs.
2026-07-10 19:06:51 +02:00
edde746 e6ffc54c56 style: format image_cache_service 2026-07-10 19:06:51 +02:00
edde746 b5d8ff5332 fix(metadata-edit): use 16:9 poster picker for episodes
close #1519
2026-07-10 12:45:35 +02:00
edde746 7509077d63 perf(tv): reduce animated semantics work 2026-07-10 12:07:19 +02:00
edde746 cbabdf2151 fix(images): prevent artwork limiter permit leaks 2026-07-10 12:07:19 +02:00
edde746 1acc83572e perf(tv): reduce navigation rebuilds and artwork fan-out 2026-07-10 12:07:19 +02:00
edde746andl3gitpanda b46107cb58 fix(tv): dismiss on-screen keyboard on companion-remote search submit
close #1514

A query submitted from the companion remote now behaves like a submit
instead of focus-to-type: TvKeyboardController closes an already-open
OSK and lands focus on the input without reopening it, then the shared
handleSearchSubmit path runs the search and focuses the first result.
Also guard DebouncedMediaSearch against selection-only controller
notifications re-arming the debounce into a duplicate fetch.

Co-authored-by: l3gitpanda <12003346+l3gitpanda@users.noreply.github.com>
2026-07-10 08:48:38 +02:00
edde746 1261852514 fix(ci): regenerate Trakt catalog model 2026-07-10 07:32:02 +02:00
edde746 04daf89a20 feat(explore): Explore tab with catalog detail, search, and Seerr requests 2026-07-10 07:10:15 +02:00
edde746 f94b8c6586 feat(settings): Services hub, Seerr sign-in, and all-locale strings 2026-07-10 07:09:29 +02:00
edde746 b4943f8c50 feat(seerr): Seerr API, account provider, and Explore catalog source 2026-07-10 07:08:42 +02:00
edde746 2aa084287d feat(explore): MyAnimeList catalog source 2026-07-10 07:07:54 +02:00
edde746 dcdbbc8483 feat(explore): match catalog items to library content via reverse external-id lookup 2026-07-10 07:07:07 +02:00
edde746 4e6ea5b4c5 feat(trakt): catalog API and shared catalog core (watchlist, discover, recommendations) 2026-07-10 07:06:21 +02:00
edde746 ede80d225f refactor: shared coalescer, profile-scoped prefs key, and form-error helper 2026-07-10 07:05:35 +02:00
edde746 397de2698d feat(livetv): flat M3E card redesign for guide, recordings, and details 2026-07-10 06:41:15 +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 2c13438d71 fix(tv): retain live TV session while backgrounded 2026-07-09 23:22:27 +02:00
edde746 fe5d6d8d96 fix(music): play audio playlists from context menus 2026-07-09 23:22:27 +02:00
edde746 e2ad28ca32 fix(downloads): retain offline media after logout 2026-07-09 23:22:27 +02:00
edde746 ed183a855e ci: track Android Gradle wrapper 2026-07-09 17:55:06 +02:00
edde746 5867809560 fix: resolve Jellyfin, logout, playback, and Android regressions 2026-07-09 17:14:45 +02:00
edde746 a4a2942546 fix(tv): release native AV pipeline when app stays backgrounded
A paused-but-alive player keeps its MediaCodec decoders and tunneled
passthrough AudioTrack while Plezy sits in the background, which can
degrade playback in every other app on shared-pipeline TV SoCs until
force-stop. After a 30s grace on Android TV the player is stopped
(retaining session state); returning reloads in place, or re-tunes the
channel for live TV.
2026-07-09 15:23:29 +02:00
edde746 71d644c4ae fix(macos): stop CPU spin when display sleeps with video paused
When the display/system sleeps with a video paused, the window server
stops completing CAMetalLayer presents and mpv's MoltenVK swapchain wedges,
pinning one core at 100% until the app exits. occlusionState does not
reliably change on display sleep, so observe NSWorkspace screensDidSleep/
Wake to hide the metal layer (and drop the playback activity) while dark,
restoring on wake with a forced redraw when paused to avoid a stale frame.

Pairs with MPVKit v1.0.10, which gates libmpv presentation on the layer's
hidden state (check_visible) so presents actually stop while hidden.
2026-07-09 11:56:13 +02:00
edde746 574b8d524f fix(ios): don't claim audio session at app launch
The non-mixing playback session was activated in didFinishLaunching,
so merely opening the app stopped other apps' audio. Keep the category
configuration; playback start activates the session.
2026-07-07 08:55:26 +02:00
edde746 94852e49cb fix(ios): stop reclaiming audio session while paused in PiP
close #1496

Bumps os_media_controls so the audio session is only activated on
playing-state pushes; paused/metadata pushes no longer re-interrupt
other apps' audio (which caused the 1-2 Hz play/pause loop).
2026-07-07 08:54:31 +02:00
edde746 4a3295b7fd fix(player): handle Plex multi-episode files in up-next and watch state
close #1500
2026-07-06 18:39:45 +02:00
edde746 ef75887504 style: apply analyzer lint fixes across lib
Initializing formals, unawaited() on fire-and-forget futures, unused
import removal, wildcard params, and mounted vs context.mounted.
2026-07-06 15:56:56 +02:00
edde746 49d4b433a7 fix(music): show now-playing rail item only on TV
Desktop already has the mini-player as the way back into the
now-playing screen; the rail item is redundant there.
2026-07-06 15:56:01 +02:00
edde746 5bb8b1b9f2 feat(music): add persistent volume control
Independent of the video player volume; persisted via SettingsService
and re-applied to every audio player instance.
2026-07-06 15:55:06 +02:00
edde746andClaude Fable 5 04c0714881 chore: bump os_media_controls for lock-screen artwork fixes
Pulls edde746/media_controls@71b96f8: iOS/macOS guard async artwork
downloads against track changes (slow art no longer lands on the next
track) and clear stale artwork when a track has none.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-06 15:33:21 +02:00
edde746andClaude Fable 5 5f7925f350 fix(music): scope current-track highlight per server
TrackRow compared bare ids (server-scoped rating keys), so a track on
another server with the same id lit up as currently playing. Compare
globalKey (serverId:id) like the rest of the queue engine.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-06 15:32:26 +02:00
edde746andClaude Fable 5 226be37f85 fix(music): mark only the active sleep timer preset selected
All timed presets shared one 'timed' bool, so arming any of 15/30/60
minutes checkmarked all three. The service now retains the armed
duration (sleepTimerDuration) and the menu marks the matching preset.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-06 15:31:29 +02:00
edde746andClaude Fable 5 32c4810c21 fix(music): resync gapless state when queue edits race the track boundary
A queue edit can un-arm the next entry in the same instant mpv rolls
into it; the resulting transition was dropped as unexpected, leaving the
UI and progress reporting on the finished track for the entire next
file. Remember the cleared arm (generation-gated) so the transition is
still adopted, and handle the armed track no longer being in the queue:
advance to the queue's real next, or park when nothing follows. Also
fixes the latent fallthrough that left the cursor on the finished track
when the armed track vanished from the queue.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-06 15:30:18 +02:00
edde746andClaude Fable 5 28bc4a5df8 feat(player): handle stop/skip/speed media-session commands
The platforms advertise stop, skip forward/backward, and playback-rate
commands by default, but both the music and video handlers silently
dropped them (Android Auto/Bluetooth stop and FF/rewind did nothing;
iOS/macOS showed a dead rate control). setControlsEnabled now manages
those controls: music handles Stop and in-track skips and stops
advertising a speed control; video handles Stop (exit, matching the
companion remote), skips via a shared relative-seek helper, and rate
changes through player.setRate. Skip commands stay off on iOS/macOS
where they would displace the next/previous lock-screen buttons.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-06 15:28:24 +02:00
edde746andClaude Fable 5 73be8ab1c8 fix(music): convert SAF content:// downloads for gapless arming
setNext() appended raw content:// URIs that mpv cannot open, stalling
playback at the SD-card-download track boundary. Convert to fdclose://
like open(), track the armed fd, and reclaim it via a new closeContentFd
method when the entry is dropped unplayed (close only when provably
unconsumed — playlist-pos 0 before and after the remove; leak on doubt).
The playlist-pos pre-check also keeps the clear path from removing the
playing entry when mpv rolls into the armed track mid-clear.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-06 15:26:59 +02:00
edde746 4de38f4b17 fix(music): hide album track numbers in queue rows
Album-relative numbers read as a sorting bug in a mixed-album queue;
the leading slot stays so titles align with the equalizer row.
2026-07-06 12:17:03 +02:00
edde746 adf732a1a1 fix(ui): app-bar tab chip strips scroll instead of overflowing
Shared TabChipStrip replaces the plain Rows in libraries/downloads/live
TV app bars (and live TV's hand-rolled scroll wrapper); chips center
themselves on focus so d-pad reaches off-screen tabs.
2026-07-06 12:17:03 +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 fcdef343eb fix(music): sheet safe-area, swipe-to-dismiss, hide mini-player under sheets 2026-07-06 10:51:19 +02:00
edde746 a4d40486c1 fix(music): responsive artist header on desktop
Wide layouts put the circular portrait beside a left-aligned
name/genre/bio/actions block (mirroring the album header) instead of
a centered mobile column floating over the left-hugging albums grid.
2026-07-06 00:23:09 +02:00
edde746 338a53a036 fix(player): comma-safe http header delivery to mpv
mpv's http-header-fields string-list parser splits on commas inside
header values, so X-Plex-Device on Apple hardware (model ids like
Mac17,9) produced a colon-less garbage header line that Plex rejects
with 400 'Error parsing HTTP request' — first hit by music direct play
on macOS, latent in the mpv video path too. Deliver headers via
change-list append items (open) and per-entry -clr/-append loadfile
options (gapless arming), each item verbatim and %len%-quoted against
the outer option-list split.
2026-07-05 23:58:59 +02:00
edde746 4da489d4dc i18n(music): translate music player strings to all locales 2026-07-05 22:16:36 +02:00
edde746 db18ee4b34 feat(music): android background playback via media_controls foreground service
Bumps os_media_controls to 4f4b28f3: MediaStyle foreground service with
a JUnit-tested promote/demote/stop policy, artwork URL download (also
fixes video lock-screen art), and task-removal teardown that can't leak
orphan notifications. The music service opts into background mode per
session and requests POST_NOTIFICATIONS before first playback.
2026-07-05 21:52:58 +02:00
edde746 764345f021 feat(music): audio downloads and music home rows
Album/artist downloads expand to tracks with pinned parent metadata,
aggregate progress, container deletes with reference-counted album
covers, and a Music tab on the downloads screen with fully offline
album playback. Home rows include music libraries (fixes plex hub
items being filtered to video types) and audio playlists join
download/sync rules.
2026-07-05 21:51:37 +02:00
edde746 7d6a747aa7 feat(music): now-playing, mini-player, queue and lyrics UI
Persistent mini-player over the profile navigator (route-aware
suppression, bottom-bar inset), slide-up now-playing screen with
mobile/desktop/TV layouts (blurred-art background, pause shape morphs,
dpad seek chain), queue sheet with reorder/remove/jump, synced lyrics
view, TV rail now-playing item, sleep timer, and audio playlist
playback. Adds download affordances on music surfaces.
2026-07-05 21:50:11 +02:00
edde746 422db75b5b feat(music): mpv audio playback engine with gapless queue service
Audio-only mpv core on every platform (dedicated
com.plezy/mpv_audio_player channels): parameterized android/windows/
linux mpv plugins and a new apple MpvAudioPlayerCore, all skipping
video/window paths (vid=no, audio-display=no, gapless-audio=weak).
MusicPlaybackService drives an in-memory queue with shuffle/repeat,
file-loaded-event gapless arming (property edges coalesce and the
android bridge drops them), per-track progress reporting, OS media
controls, audio focus, sleep timer, and error auto-skip.
PlaybackCoordinator enforces one live native player: starting video
disposes the audio core first.
2026-07-05 19:26:28 +02:00
edde746 05a631415e feat(music): music browsing UI — square cards, library groupings, artist/album screens
Un-gates music libraries; adds CardShape.square plumbing (circular
artists), artists/albums/tracks browse groupings with per-grouping
sorts, audio playlists tab, artist + album detail screens with dpad
track rows, music context-menu actions, and a stub MusicPlaybackService
seam for the upcoming engine.
2026-07-05 17:20:28 +02:00
edde746 fff559da06 feat(music): add music data layer for plex and jellyfin
Backend-neutral fetchArtistAlbums/fetchAlbumTracks/fetchInstantMix/
fetchLyrics, lyrics model + LRC parser, audio quality presets, music
transcode (plex /music universal, jellyfin audio PlaybackInfo profile),
station/InstantMix radio, music search, playlist type derivation, and
audio download resolution.
2026-07-05 15:55:52 +02:00
edde746 c87a9060f9 chore: tidy formatting and local ignores 2026-07-05 14:32:21 +02:00
edde746 ee3c75ba9e fix(playback): remember last played media version for continue watching
close #1492
2026-07-05 14:29:57 +02:00
edde746 90c4fe5458 feat(audio): add Kodi-style stereo downmix with center channel boost 2026-07-05 14:29:57 +02:00
edde746 97901cba87 chore(deps): bump background_downloader 2026-07-05 14:29:57 +02:00
edde746 7f81a0d4fd fix(windows): retry connectivity listener registration outside input-sync calls
INetworkListManagerEvents Advise fails with
RPC_E_CANTCALLOUT_ININPUTSYNCCALL when the platform thread is inside an
input-synchronous call at subscribe time, and an OnListen error can
only surface in Dart as an uncatchable fatal FlutterError. The fork now
retries the registration via a message-loop timer and reports only
persistent failures through the event sink, where stream onError
handlers observe them.
2026-07-05 14:16:05 +02:00
edde746 173117602a fix(android): vendor saf_util with a sound pendingResult lifecycle
saf_util 2.0.0 leaves an already-answered MethodChannel.Result behind
in pendingResult: unrelated activity request codes consume (and answer)
the pending picker, and a failed picker launch replies without clearing
the pending state. The next delivery for request 1001 then replies a
second time — IllegalStateException "Reply already submitted" in
MainActivity.onActivityResult, crashing the process.

Vendored at packages/saf_util (BSD-3) with take-and-clear reply
ownership, request codes scoped to the plugin's own pickers, and a
guarded reply on the teardown race.
2026-07-05 14:16:05 +02:00
edde746 d9130cd932 fix(detail): guard on-deck fallback against a disposed screen
_ensureFallbackOnDeckEpisode is reached through unawaited fetch
continuations and reads providers through State.context — leaving the
screen before the season fetch settled crashed on the null-check in
the context getter.
2026-07-05 14:16:04 +02:00
edde746 be072365fc fix(player): scope event-channel cancel to the owning instance
Player backends share static per-backend EventChannel names, so two
overlapping instances (episode handoff, quick exit/reopen) collide:
the newer instance's listen displaces the older sink, the older
instance's late cancel then tears down the newer stream — silently
freezing its events — and the final cancel earns the engine's
"No active stream to cancel" reply, which the framework can only
report as an uncatchable fatal FlutterError (363 events).

Track the owning instance per channel: only the current owner sends
the native cancel; a displaced instance drops its dead subscription.
Residual benign teardown races are dropped in beforeSend.
2026-07-05 14:16:04 +02:00
edde746 61f7d33fdf fix(profiles): treat vault decrypt failures as lost credentials
A failed MAC check (key/ciphertext divergence: restored backup,
clobbered prefs, racing key generation across isolates) threw from
CredentialVault.reveal on the startup profile-settings path and
crash-looped the app until data was wiped — one device logged 31
fatals in 16 minutes on 2.8.0.

Decrypt failure now means the credential is lost, never a crash:
reveal() returns null, ProfileConnectionRegistry maps it onto the
existing empty-token lazy-fetch sentinel and heals the row so later
boots re-acquire the token instead of re-failing, and
revealConnectionConfig degrades tokens to empty strings without
marking them migrated. Key init also reloads prefs before deciding to
generate and re-reads after writing, adopting whatever landed so all
isolates converge on a single key instead of orphaning ciphertext.
2026-07-05 14:16:04 +02:00
edde746 3766b2c2d3 fix(remote): handle connect failures of losing race candidates
Race candidates created with IOWebSocketChannel.connect fail their
ready future on connect timeout or an unreachable address, and nothing
observed it — web_socket_channel reports connect errors there, so every
losing candidate surfaced as an unhandled fatal (the tracker's single
largest crash source, ~1300 events: bare 5s TimeoutException plus the
recurring no-route-to-host WebSocketChannelExceptions). The stream
listener's onError only covers post-connect errors. Swallow and log the
ready failure per candidate; the race outcome is unaffected.
2026-07-05 14:16:04 +02:00
edde746 0dfe013928 fix(home): include clip libraries in per-library home rows
close #1476
2026-07-05 09:43:34 +02:00
edde746 25c7893a8e fix(windows): report native crashes through the inproc sentry backend
Crashpad uploads minidumps straight to the DSN's /minidump endpoint,
which our tracker does not implement — every native Windows crash
(e.g. faults inside libmpv threads, #1490) was silently dropped.
The inproc handler stackwalks at crash time and reports through the
regular envelope endpoint, and crashpad_handler.exe/crashpad_wer.dll
leave the bundle.
2026-07-05 09:10:46 +02:00
edde746 28ec9cacb2 perf(tv): evict non-adjacent season episode pages on low-end hardware
Visited seasons' 200-item pages were retained for the screen's
lifetime — irrelevant for a 3-season show, tens of MB of Dart heap for
a 30-season one. Low-end TV now keeps the prefetch window (selected
season plus/minus one); evicted seasons transparently refetch through the
existing unloaded-hub path.

Ref #1349
2026-07-05 09:10:46 +02:00
edde746 aaf27a7f9e fix(android): extend the RSS eviction watchdog beyond desktop and log tier signals
Android trim callbacks are best-effort — LMK can kill without ever
delivering one — yet the RSS watchdog only ran on desktop with a fixed
1.5GB bar no 2GB TV box ever reaches before dying. The watchdog now
runs on Android with a threshold scaled to device RAM, an eviction
floor and cooldown against refetch churn, and a lower backgrounded bar
since a paused app is LMK's first candidate.

The logs-screen header and startup line now record the renderer, the
effects tier with its raw hardware signals, and the boot RSS, so
uploaded reports answer whether the reduced tier engaged.

Ref #1349
2026-07-05 09:10:36 +02:00
edde746 0fb0c7fb4c perf(android): cap engine memory pools on low-RAM devices
Flutter's imageCache budget only bounds decoded CPU bitmaps: Skia's GPU
resource cache is sized from the surface area (hundreds of MB on a
4K-composited TV) and the Dart old gen defaults to a large fraction of
physical RAM. Both drive LMK kills on 2GB boxes. Cap them via engine
shell args on hardware matching the reduced-tier triple.

Ref #1349
2026-07-05 09:10:27 +02:00
edde746 f97ab03111 fix(images): bound artwork decodes on both axes and never fetch unsized originals
The decode clamp was height-only, so ultra-wide originals could decode
far past the display budget, and two small-slot fallbacks skipped
server-side resizing entirely — handing multi-megapixel originals to
the decoder behind tiny slots. The TV spotlight background's offline
branch had no decode bound at all. All artwork now flows through a
shared both-axes bound (ResizeImagePolicy.fit: aspect-preserving,
scale-down only), and the reduced tier gets matching thumb/poster
decode caps.

Ref #1349
2026-07-05 09:10:20 +02:00
edde746 3981fac0db feat(settings): surface Manage Libraries and raise the desktop sheet cap
Extract the manage/reorder-libraries sheet from the libraries screen into
a shared widget with a public launcher, and add a settings tile (top
group, gated on having libraries) that opens it. The libraries screen
keeps its side-nav refresh and re-select-on-hide behavior via launcher
hooks; the TV dialog path lives in the launcher so the entry works there
too.

Desktop overlay sheets defaulted to a fixed 400px max height, which left
the reorder sheet ~280px tall. Both default-constraint sites now use 75%
of window height, matching mobile.

close #1468
2026-07-05 09:08:29 +02:00
edde746 4a43ae2771 fix(android): auto-recover frozen video after pause/resume on stalling decoders
Some TV SoCs (Amlogic Mi Box class) stall the MediaCodec output path
after pause/resume: audio and the clock keep advancing but the picture
freezes until a seek flushes the codec. Arm a watchdog on every
transition to playing with a warm decoder; if rendered frames stop
advancing while the position moves, recover with a 250ms seek-back
(same-position seeks are short-circuited without a codec flush),
capped per session and logged for field diagnosis.

close #1454
2026-07-05 09:08:29 +02:00
edde746 c4184c6148 fix(ci): restore sanity checks 2026-07-05 08:34:55 +02:00
edde746 87eaafe3c4 chore(deps): update MPVKit to v1.0.9
close #1420, close #1457, close #1484
2026-07-05 08:15:00 +02:00
edde746 aee48f6956 feat: extend Force TV mode to desktop for home-theater setups
Initialize TvDetectionService on every platform so the existing
force_tv_mode setting drives the 10-foot TV interface on Windows,
macOS, and Linux, and surface the toggle in appearance settings there.

Desktop keeps real-mouse behavior in TV mode: InputModeTracker still
flips between pointer and keyboard modes (cursor hidden while
keyboard-driven), segmented controls keep their hover affordance, and
the settings backup section stays available (only Android TV lacks a
document picker).

Adds PlatformDetector.debugSetIsDesktopOSOverride so TV-device
simulations in widget tests don't inherit the desktop test host's
platform.

close #1409
2026-07-05 07:08:40 +02:00
edde746 3d5a1ec966 fix: build break from Sentry.configureScope FutureOr return
The pinned sentry fork declares configureScope as FutureOr<void>,
which unawaited() rejects; wrap in Future.sync to keep the
fire-and-forget behavior.
2026-07-05 06:49:46 +02:00
edde746 2e4ba352f4 fix: multi-trigger audio recovery after Windows sleep/wake
The two prior attempts at #783 failed for lack of a reliable trigger:
re-setting audio-device to its own value is a no-op, and gating
recovery on audio-device-list changes while current-ao is null never
fires in the real flows (a paused player only discovers the dead
WASAPI session at unpause, when the device list is already stable;
a zombie session that consumes samples silently never goes null).

Replace the single gate with a recovery scheduler on the mpv event
thread, ticked every wait-event timeout:

- WM_POWERBROADCAST resume (both PBT variants, via the plugin's
  existing window-proc delegate) requests unconditional ao-reloads at
  +1.5s and +6s after wake — the only trigger that reaches zombie
  sessions; the second shot covers a first reload landing while the
  audio stack is still restoring. The handoff is a single atomic
  flag: no mpv calls or timers from the platform thread, so there is
  nothing to clean up on dispose.
- current-ao falling to null arms clock-driven retries (budget 5,
  x2 backoff capped at 8s); retries must be clock-driven because a
  failed ao-reload falls back to null without a property change
  event. A device-list change while null refreshes the budget, and
  audio-device-list is now observed natively so recovery does not
  depend on the Dart side.

Every trigger, attempt, and outcome logs as [mpv:audio-recovery] at
warn level so exported logs from the reporter are actionable if this
still misses. Verified with synthetic PBT_APMRESUMEAUTOMATIC while
playing and while paused: full WASAPI reinit both times, audio
restarts on unpause.

Ref #783
2026-07-05 06:49:38 +02:00
edde746 31e8c2e1e0 fix(plex): coerce PlexHomeUser fields against account-API drift
Same clients.plex.tv surface that broke in #1488: /home/users powers
first sign-in and profile setup, so a drifted scalar shape would kill
the whole fetch. Coerce strings, bools, and ints tolerantly instead of
hard-casting.
2026-07-05 06:23:41 +02:00
edde746 96510f8aac fix(plex): tolerate account-API schema drift in profile and switch parsing
Around July 3 plex.tv started returning the profile language-list fields
(defaultAudioLanguages, defaultSubtitleLanguages, mediaReviewsLanguages)
as comma-separated strings instead of arrays. The generated cast threw on
the successful 201 /switch response, dropping the freshly minted Home
user token: every rebind failed, the binder retried a /switch mint every
1-2s, and the app sat permanently in offline mode even after re-signing
in. Accounts without language prefs set were unaffected, which is why
the breakage looked sporadic.

Parse the language lists with a CSV-aware coercion, and make
UserSwitchResponse.fromJson strict only about authToken: decorative
fields now coerce tolerantly and a broken profile blob falls back to
defaults, so account-API drift can never brick token minting again.

close #1488
2026-07-05 06:23:34 +02:00
edde746 6e83aeda88 fix(hubs): drop deleted items from recommended and home continue watching
The library Recommended tab never subscribed to deletion events, so
"Delete from server" left the episode sitting in Continue Watching
until a full reload. Make the tab DeletionAware (remove in place across
all hubs, then resync) and give DiscoverProvider the same subscription
so the home row and hubs drop deleted items too.

close #1486
2026-07-05 05:58:52 +02:00
edde746 7b1c150e18 feat(android): enable impeller on capable TV devices
Impeller was blanket-disabled on TV in e759dafa as a precaution when the
Tensor/NVIDIA fallbacks landed, and #749 (API 30 Fire TV Cube freezing
until the Skia build) validated it. Flutter has since deprecated the
Impeller opt-out, so start walking the disable back where the hardware
can take it: TV devices on Android 12+ with a Vulkan 1.1 driver use
Impeller, while Fire OS (modern API levels on GPUs whose drivers can't
back it up), pre-12 boxes, and the existing NVIDIA/Huawei/e-waste quirks
stay on Skia. Tag Sentry events with the active renderer so TV
regressions are attributable while this rolls out.
2026-07-05 02:28:51 +02:00
edde746 11f7fd766d perf(library): cut scroll jank in card grids and hub rows
Profile traces showed 100-370ms UI-thread frames while scrolling the
library screen, dominated by rebuilding and inflating media cards.

- Gate per-card focus/pointer chrome on input mode: FocusableWrapper
  skips the scale/border wrappers and creates its AnimationController
  lazily outside keyboard mode, and ClickableCursor plus the card tap
  region only build MouseRegion/InkWell machinery on desktop - TV and
  touch use a bare GestureDetector. Hub cards also drop their outer
  gesture wrapper outside keyboard mode; the card's own tap region
  always won the gesture arena anyway.

- Memoize sliver children (SliverChildMemo): browse/collections grids
  and hub rows return identical widget instances for unchanged items,
  so delegate swaps from pagination, watch-state, and deletion
  setStates no longer rebuild every realized card inside layout. The
  browse tab prunes the memo in lockstep with focus-node eviction so a
  cached card can never resurrect a disposed FocusNode.

- Budget fresh inflation (CardInflationBudget): while a scrollable is
  moving in pointer/touch mode at most one new card inflates per frame,
  the rest render as SkeletonMediaCard and upgrade on following frames.
  Hub rows also stop pre-inflating 250px of off-screen cards on entry.

Device traces: worst frame 373ms -> 103ms, per-card build 3.6ms ->
2.4ms median; remaining row-entry work is spread across frames.
2026-07-05 02:23:42 +02:00
edde746 d80a1ed15a feat: report real device identity to media servers
Resolve platform, hardware model, and friendly device name once via a
shared DeviceIdentityService and send them to both backends: Plex gets
a real X-Plex-Platform plus X-Plex-Device/X-Plex-Device-Name (shown as
Player in dashboards/Tautulli), Jellyfin gets the device name in the
MediaBrowser auth header. Transcode and live-TV decision requests keep
their pinned platform names, which Plex validates server-side.

close #1270
2026-07-05 01:22:20 +02:00