From 0187ee179e6c17c25edd4444778a6893b1aba94d Mon Sep 17 00:00:00 2001 From: Duncan Tourolle Date: Sun, 23 Aug 2026 20:20:01 +0200 Subject: [PATCH] fix(android): draw the subtitles the player already decodes MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Turning a subtitle on did nothing even after DR-259 made them load. ExoPlayer decodes subtitles and delivers them to a listener; it draws none itself. A PlayerView would supply the view that does, but native video here is a bare TextureView the WebView composites over β€” so nothing held the cues and every one was decoded, delivered and dropped. There was no onCues, no TextOutput and no SubtitleView anywhere in the app, and media3-ui was not even a dependency. The gap was invisible for as long as every subtitle URL 404ed: with no text track to select there was never a cue to lose, so fixing the URL is what exposed it. media3-ui's SubtitleView now takes each CueGroup and is attached at index 1 of the content view β€” above the video, still below the WebView, so cues sit over the picture and under the app's own controls. It is fitted to the letterboxed video rect rather than the screen, so cues stay inside the picture and follow it on rotation, and is removed by the same teardown that detaches the surface (the defect DR-184 exists to prevent). Verified on a device: track selected with no "Invalid subtitle track index", SubtitleView attached at the fitted rect per the live view hierarchy, and cues legible on screen during playback. TRACES: UR-020, UR-003 | DR-260 --- CHANGELOG.md | 13 ++++ docs/requirements.md | 1 + src-tauri/android/app/build.gradle.kts | 4 ++ .../dtourolle/jellytau/VideoOverlayManager.kt | 26 +++++++ .../jellytau/player/JellyTauPlayer.kt | 71 +++++++++++++++++++ 5 files changed, 115 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index ed2e6477..1f2fbb59 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,6 +9,19 @@ generated trace matrix lives in [docs/traceability.md](docs/traceability.md). For how long each fixed defect had been shipping before it was found, see [docs/defect-windows.md](docs/defect-windows.md). +## Unreleased + +### πŸ› Fixes + +- **Subtitles appear on screen on Android.** Turning one on did nothing, even + once they were loading again: the player hands finished subtitles to a view + that draws them, and on the native Android path there was no such view β€” so + every cue was decoded, delivered and dropped. There is one now, sitting over + the picture and under the controls, following the video's shape when the + screen turns. This was hidden behind the loading failure fixed in v0.11.1; + with nothing to select, there had never been a cue to lose. (UR-020, UR-003 β†’ + DR-260) + ## v0.11.1 Four fixes. Two had been present since the first release and were found on a diff --git a/docs/requirements.md b/docs/requirements.md index fea772b3..d502a8b1 100644 --- a/docs/requirements.md +++ b/docs/requirements.md @@ -453,6 +453,7 @@ Internal architecture, components, and application logic. | DR-257 | A container's children are ordered by **what the container is**, decided in Rust. The frontend pinned `SortBy=SortName` onto every drill-down, so a Jellypod podcast β€” a Jellyfin channel folder whose plugin returns episodes newest-first and prefixes played ones with "[Played]" β€” listed alphabetically, which both discarded the release order and clumped every heard episode at the top. `ChannelFolderItem` with `is_folder` now maps to its own `MediaKind::ChannelFolder` rather than collapsing into `Folder`, which is what makes the two distinguishable at all; `default_listing_sort` maps that kind to `PremiereDate` descending and every other container to `SortName` ascending, and a caller that names no container still gets no `SortBy`, so paths relying on the server's own order (a playlist's stored order) keep it. An explicit sort always wins. The offline leg of the cache/server race applies the same order, so the cached list does not flash in name order before the server's arrives. The store now names the container and never a sort field β€” the ordering rule is domain vocabulary, the same division as `SearchScope` | Repository | UR-007 | Done | | DR-258 | An audio-track change is honoured by **re-opening the stream** when the stream cannot carry the track. Jellyfin builds a transcode around one `AudioStreamIndex`, so the alternate tracks are not in it β€” but the native path only ever called `setAudioTrack(n)`, which indexes ExoPlayer's audio track *groups*. On Android that is the common case, since any source whose default audio codec the device cannot decode is transcoded: ExoPlayer held one audio track while the menu listed every track in the file, so every selection warned `Invalid audio track index` and was dropped, leaving the default track playing with nothing in the UI saying so. `determine_audio_track_switch_strategy` now decides by whether the stream in front of the engine carries the track at all β€” a direct play still selects in place, a transcode is re-negotiated at the chosen index and resumed. Where it resumes is the player's answer, not the UI's: the native path has no `