Files
jellytau/CHANGELOG.md
T
dtourolle acddcdd6fa fix(playback): force a transcode when the webview cannot decode the audio (DR-149, 0.4.8)
Advertising a webview-shaped profile (DR-148) was necessary but not
sufficient. Probing the server directly showed Jellyfin 10.11.5 enforces a
DirectPlayProfile's Container and VideoCodec — excluding either returns
SupportsDirectPlay:false with TranscodeReasons=ContainerNotSupported /
VideoCodecNotSupported — but ignores its AudioCodec entirely: an E-AC-3
track is still offered for direct play against a profile listing only
aac,flac,mp3,opus,vorbis. Neither a VideoAudio CodecProfile forbidding the
codec nor MaxAudioChannels:2 against a 6-channel track changes the answer,
so no profile the client can send fixes this and the picture plays silent.

The client therefore stops delegating a question it can answer itself. The
negotiated source's audio is checked against what the webview decodes, and
an undecodable track forces the existing h264/aac HLS transcode regardless
of the server calling direct play fine; direct_play and needs_transcoding
are corrected to match so the frontend and the reporting path agree with
the URL actually used. The track judged is the one that would be served —
the default, else the first — since a supported track further down is not
the one that plays. A source with no audio, or a codec the server did not
name, is left alone rather than transcoded on a guess.

Test-first: the new tests failed against the old behaviour before the
decision existed. Verified on a motorola edge 30 by the audio HAL, not by
ear — the same E-AC-3 episode logged isMusicActive=true once and 58
ACDB-LOADER lines under this build, against 0 and 0 on 0.4.6, where an AAC
file in the same session produced 16 and 116. No FATAL EXCEPTION, so R8 on
the signed release build is unaffected.

Also carries in-flight subtitle-track work authored in a parallel session
(subtitleTracks, VideoPlayer, player/media, bindings) at the user's
request, so the tag matches the APK verified on device.
2026-08-11 20:07:11 +02:00

215 lines
11 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# Changelog
All notable changes to JellyTau are documented here.
Entries are grouped by the capability they change, not by commit. Requirement
IDs in parentheses point at [docs/requirements.md](docs/requirements.md); the
generated trace matrix lives in [docs/traceability.md](docs/traceability.md).
## v0.4.8
### 🐛 Fixes
- **Video with an undecodable soundtrack now transcodes instead of playing
silent.** Advertising a webview-shaped profile (v0.4.7) turned out not to be
enough: Jellyfin 10.11.5 enforces a direct-play profile's container and video
codec but ignores its audio codec, offering an E-AC-3 track for direct play
against a profile listing only AAC — and no `CodecProfile` or channel limit
changes that. The client now checks the track it would actually be served
against what its renderer can decode and forces the h264/AAC HLS transcode
when it cannot, rather than trusting the negotiation.
(UR-004 → DR-149)
## v0.4.7
### 🐛 Fixes
- **Video plays with sound on devices that ship a Dolby decoder.** The audio
codec list sent to Jellyfin came from `MediaCodecList`, which describes
ExoPlayer — but video does not play through ExoPlayer: it renders in the
webview `<video>` element, which decodes far less. A phone whose vendor
licenses Dolby therefore advertised `ac3`/`eac3`, got a direct play, and
showed full picture with no audio, while a leaner device claimed neither
codec, received an AAC transcode, and played the same file correctly. The
video direct-play profile is now narrowed to what the webview can decode;
audio-only playback is genuinely the native player's and keeps the full list,
so music is not transcoded needlessly.
(UR-004 → DR-148)
## v0.4.1
### 🐛 Fixes
- **The lockscreen pause works while a video's audio plays in the background.**
The handoff starts native audio and only then tears the WebView `<video>`
down — and that teardown fires a DOM `pause` the frontend reports like any
other, which left the controller believing webview media was still the
player. Transport stayed aimed at it: pressing pause on the lockscreen sent a
control command to a `<video>` that no longer existed while the native player
carried on, and the element's parting position report dragged the displayed
time backwards. A handoff is now tracked explicitly, so it hands transport to
the native backend and ignores what the dying element still reports. A pause
made from the lockscreen also survives the return to the app, instead of being
undone by the play state captured when the handoff began.
(UR-040, UR-005 → DR-052, DR-097)
## v0.4.0
### ✨ Features
- **Favourites, across libraries.** A `/library/favorites` page renders
favourites from every library with All / Movies / Shows / Music scope tabs,
reusing the standard grid so card shape still follows the media — a mixed All
tab reads as posters, squares and thumbnails side by side. Home carries
favourite rows below Recently Added, and a row with no items does not render
at all, so a fresh install shows no empty rows. Server favourite state is
mirrored into the local database as results are cached, so offline browsing
sees the same favourites as the server; a toggle made offline is never
overwritten by a stale server value before it has been pushed.
(UR-067, UR-069 → DR-113, DR-114, DR-115, DR-117, DR-118)
- **Search answers from the local index.** The instant leg read only downloaded
items, so with no downloads it returned nothing and every keystroke fell
through to a full `Recursive=true` server query. It now reads the whole synced
catalog through the same availability CTE `get_items` uses, gated on the same
`include_catalog_browse` flag, so search and browse cannot diverge. The index
also gained MusicArtist, Playlist and People — the very groups search sorts
results into. Re-indexing moved from a frontend startup call to a Rust
background task with a 6h TTL, so a long session no longer searches a stale
catalog. (UR-065 → DR-108, DR-110, DR-111)
### 🐛 Fixes
- **Playback no longer restarts an episode at random on a flaky connection.**
Background audio-only playback of a video streams a progressive mp3 transcode
over plain HTTP, which is chunked and so declares no length: when the
connection dropped mid-episode, ExoPlayer saw end-of-input and reported
`STATE_ENDED`, indistinguishable from the real end. The app ran its
end-of-episode logic mid-episode and playback parked in `STATE_ENDED`, where
the next play intent from the lockscreen, notification or a Bluetooth
reconnect seeks an ended player to position 0 — surfacing as "the episode
randomly restarted". The item's runtime is now what decides: an end reported
well short of it re-opens the stream where it stopped. (UR-040 → DR-129)
- **A network hiccup no longer kills playback outright.** Music and video
declare a length, so a cut connection reaches them as an *error* rather than a
phantom end — and every error stopped the player. A recoverable error now gets
one bounded attempt at re-opening the stream where it stopped, with a growing
backoff, leaving the rest of the queue intact. On Linux, MPV additionally
reconnects inside the demuxer so ordinary blips never surface at all, and
`EndFile(ERROR)` — previously a bare log line that left playback halted while
the UI still showed "playing" — is now reported and recovered.
(UR-004, UR-040 → DR-129, DR-130)
- **The player no longer reads 0:00 as a track ends on Linux.** MPV exposes
`time-pos` and `duration` as properties of the *loaded* file, so at EOF it
unloads and both stop resolving — reporting zero at exactly the moment
end-of-file handling asks where playback reached. The last reading seen while
media was loaded is now kept and used as the fallback. (UR-005 → DR-130)
- **Server-side deletions propagate to the local catalog.** `DELETE FROM items`
existed nowhere, so items removed on the server lingered locally forever. A
post-crawl mark-and-sweep now removes them, scoped to crawled types, skipping
downloaded items, and refusing to run after a partial crawl. Separately,
`items_fts` grew a full duplicate index on every catalog pass; it is now a
real upsert, with a migration rebuilding existing indexes. (DR-110)
- **Android system bars and display cutout are handled correctly.** (UR-066)
## v0.2.0
### ✨ Features
- **Audio settings now work on Android.** The equalizer, volume normalization
and gapless playback controls in Settings Audio previously rendered on
Android and did nothing — `ExoPlayerBackend` was the only backend that never
implemented `set_audio_settings`, and the trait's default silently reported
success while applying nothing. All three now take effect:
- **Equalizer** — the canonical 10-band ISO curve is resampled onto whatever
bands the device's equalizer actually exposes (commonly 5), by nearest
centre frequency.
- **Volume normalization** — via `LoudnessEnhancer`. Note this is a gain
stage, not a true EBU R128 normalizer like the Linux `dynaudnorm` path, so
it approximates rather than matches Linux behaviour.
- **Gapless playback** — honours the setting via `pauseAtEndOfMediaItems`
(ExoPlayer is gapless by default, so this disables it when you turn it off).
The effects re-attach automatically when ExoPlayer rebuilds its audio sink on
a format change, so the equalizer no longer stops applying part-way through a
queue. (UR-027, UR-032, UR-033 → DR-030, DR-035, DR-036, IR-004)
⚠️ **Not yet verified on a physical device.** `AudioEffect` availability and
band layouts vary by device and OEM ROM; where an effect is unavailable it is
logged and skipped rather than crashing playback.
### 📋 Documentation
- **Playback backend unification investigation.** Six new specs in
[docs/specs/](docs/specs/) record why the playback backends cannot be unified
onto a single engine: every candidate (mpv, GStreamer, libVLC) fails the same
webview-compositing constraint, because WebKitGTK/WebView2/Android WebView each
own their compositor surface and native video cannot interleave with HTML.
Audio *can* unify; video cannot. Also specifies the Android native-video spike,
a Windows native audio backend, and the `libmpv2` migration.
### 🐛 Corrected requirement statuses
These were documented as working and were not. No behaviour changed — the docs
were wrong.
- **Crossfade (UR-031, DR-034) was marked "Done (Linux only)". It is implemented
nowhere**, and is architecturally blocked on mpv: its audio chain is
single-stream, and FFmpeg's `acrossfade` requires two inputs. Real crossfade
would need two libmpv instances.
- The platform parity matrix listed crossfade as a Linux/Android gap (it is
neither) and omitted the equalizer (which was a genuine gap, now closed).
- `nativeAdapter.ts` cited tauri#10152 as blocking native Android video. That
issue is a stale feature request; the capability shipped in September 2024.
What remains unproven is SurfaceView-behind-WebView compositing, now tracked
by a spec rather than asserted as an upstream blocker.
<!--
Note: v0.1.3v0.1.5 have no entries here. Their changes are in the git log
and docs/traceability.md.
-->
## v0.1.2
### ✨ Features
- **Search results are ordered by how well they match.** A name that *starts*
with the query now outranks one matching mid-word — typing "parks" finds
"Parks and Recreation" before "Sparks of Love" — and at equal match quality a
container outranks its contents, so a series lands above its own episodes.
Ranking is applied to the instant cached results and to the merged
cache+server list alike, so the list no longer reshuffles when server results
arrive. (UR-060, DR-090)
- **Separate Shows, Episodes and People result groups.** The combined "TV Shows"
group splits into Shows and Episodes so a show never competes with its own
episodes for a slot, and a new People group means searching an actor's name
reaches their bio page. Default order is Shows → Episodes → Movies → Songs →
Albums → Artists → People; a group order saved before the split keeps the
position it was dragged to. (UR-060, DR-091)
### 🐛 Bug Fixes
- **The library header search bar works on every library page.** It previously
searched in place and depended on `/library` rendering results inline, so on
any other `/library/**` route the results were fetched and never shown.
`/search` is now the single surface that renders results, and the header bar
hands its query and scope over via the URL. (UR-049, DR-063)
- **Video smaller than the window is scaled up to fit.** Sizing only ever shrank
oversized media, so a 480p source on a 1080p display played as a small picture
in the middle of a black frame. The picture now fits whichever axis constrains
it, in both directions, preserving aspect ratio. (UR-005)
### 📋 Requirements
**Linux:** 64-bit, GLIBC 2.29+
**Android:** 8.0+
## v0.1.1 and earlier
Released before this file existed — see the git history and the release notes on
each tag.