# 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.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. ## 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.