diff --git a/CHANGELOG.md b/CHANGELOG.md index 90db6285..475e7f03 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,6 +9,30 @@ 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). +## v0.11.1 + +Two faults that had been present since the first release, both found on a +device: changing the audio track did nothing, and no subtitle would load. + +### 🐛 Fixes + +- **Changing the audio track changes the audio.** Picking a different language + did nothing on Android — the menu closed, the tick moved, and the original + track kept playing, with nothing saying otherwise. When the server is + converting a film it builds that conversion around *one* audio track, so the + others are not in the stream that arrives; the app was asking the player to + select from tracks it had never been sent. It now asks the server for the + track you picked and resumes where you were. A film playing in its original + form still switches instantly, because there every track really is present. + (UR-021 → DR-258) + +- **Subtitles load.** Every subtitle in the list was inert: the address the app + fetched them from was missing a segment, so each request came back "not + found", and a subtitle that never arrives is a subtitle the player cannot + offer. All of them had been failing this way since the first release — the + tests that were supposed to cover the address were checking a copy of it kept + inside the tests, not the one being requested. (UR-020 → DR-259) + ## v0.11.0 Video can play through the native renderer on Linux, and the machinery every diff --git a/docs/defect-windows.md b/docs/defect-windows.md index 92c8395e..d28075d3 100644 --- a/docs/defect-windows.md +++ b/docs/defect-windows.md @@ -17,8 +17,8 @@ row can be re-checked or disputed: ## Present since the first release -Nine defects date to the initial proof of concept (v0.0.1, 2026-06-23) and shipped -for between two weeks and seven weeks short of two months before anyone hit them. +Fifteen defects date to the initial proof of concept (v0.0.1, 2026-06-23) and +shipped for between two weeks and two months before anyone hit them. That is the dominant pattern here: not regressions, but original assumptions that went unexercised until a later feature leaned on them. @@ -34,9 +34,12 @@ went unexercised until a later feature leaned on them. | No `PlaySessionId`, and one hardcoded `DeviceId`, on every stream URL (DR-177) | v0.0.1 | **v0.5.5** | ~8 weeks | pickaxe | | `download_item` never recorded `media_type`; NULL read as `'audio'` (DR-135) | v0.0.1 | **v0.4.6** | ~7 weeks | pickaxe | | `download_album` read its track list from the local cache (DR-173) | v0.0.1 | **v0.5.5** | ~8 weeks | pickaxe | + | Device profile carried no `MaxAudioChannels` (DR-141) | v0.0.1 | **v0.4.6** | ~7 weeks | absence | | Streaming ceiling fixed at 20 Mbps with no way to lower it (UR-074) | v0.0.1 | **v0.5.3** (as a feature) | ~7.5 weeks | pickaxe | | Hero banner auto-rotation never restarted after a manual swipe (DR-038) | v0.0.1 | **v0.9.1** | ~8.5 weeks | pickaxe | +| Audio-track change asked the player to select a track the transcode never carried (DR-258) | v0.0.1 | **v0.11.1** | ~2 months | pickaxe | +| Subtitle URL missing its `Stream.` route segment, so every fetch 404ed (DR-259) | v0.0.1 | **v0.11.1** | ~2 months | pickaxe | ### Why they took so long to surface diff --git a/docs/requirements.md b/docs/requirements.md index 61c838b3..fea772b3 100644 --- a/docs/requirements.md +++ b/docs/requirements.md @@ -451,6 +451,8 @@ Internal architecture, components, and application logic. | DR-255 | One helper answers "what URL should an engine open". `playback_url` was gated to Android because only ExoPlayer needed it, and that gate is why a byte-identical copy was later added for the cross-platform open path — the original is invisible in a Linux build, so nothing warned. Two matches over `MediaSource` meant a new variant could be handled in one and forgotten in the other | Player | UR-081 | Done | | DR-256 | The video control bar opens **at most one menu at a time**, and opens it where it can be read. Audio track, quality and subtitles each owned a `show…` boolean that no other toggle cleared, so a second menu opened stacked over the first — two panels in the same corner, the newer one covering rows of the older, both still taking clicks. A single `openMenu` value replaces them, which makes "one menu" a property of the state rather than something every handler must remember; the desktop volume popup joins the same group through `VolumeControl`'s optional controlled-open props. Placement was the second half of the same defect: every panel was `absolute right-0` against **its own icon button**, and those icons sit mid-row, so a 220 px panel hung off the left edge of a portrait phone and half the tracks could not be read or tapped. One shared panel now anchors to the control ROW's right edge, clamped to `min(20rem, 100vw − 2rem)` wide and `min(300px, 45vh)` tall, with a full-screen dismiss layer inside the controls subtree so a tap elsewhere closes it without reaching the container's tap gestures (DR-098). The icon row itself wraps instead of overflowing — in portrait the transport controls plus nine icons are wider than the screen, which put fullscreen and close past the edge | UI | UR-020, UR-021, UR-066, UR-074 | Done | | 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 `