diff --git a/docs/api-v2-guide.md b/docs/api-v2-guide.md index d0467e99..81813b53 100644 --- a/docs/api-v2-guide.md +++ b/docs/api-v2-guide.md @@ -335,6 +335,21 @@ listing never has to load the tracks to size it, plus `is_compilation`, `musicbrainz_id` too. An `AlbumItem` also carries `artists` and `genres`, derived from its available tracks. +It carries `release_types` as the files spelled them, and since +[RFC-022](rfcs/RFC-022-playback-and-web-client.md) a `release_group` derived +from them: one of `album`, `ep`, `single`, `live` or `compilation`. Group a +discography by that field rather than by the raw types, whose vocabulary and +separators vary by tagger. The rule, applied in order: a compilation flag or +type, then a `live` type, then `ep`, then `single`; anything else, or no type +at all, is an `album`. + +`GET /api/v2/artists/{artist_id}` also carries `appears_on`: the albums of which +at least one available track credits the artist in its `artist` role, and on +which the artist is not an album artist — a guest appearance, a split single. +Credits in other roles (composer, producer, performer) do not count. It is +present on that route only; `GET /api/v2/artists/{artist_id}/albums` stays the +album-artist list. + Those two are release and artist identifiers, not the recording identifier a `SongItem` carries under the same name, and neither is read from a single file. Tracks of one album routinely disagree — a library assembled over years holds @@ -451,8 +466,13 @@ curl "https://music.example.com/api/v2/tracks/TRACK_UUID/stream?format=opus&bitr --output track.ogg ``` -Valid formats are `raw`, `mp3` and `opus`. Byte ranges apply to originals and -completed cached transcodes. A live transcode uses temporal `offset_ms` and a +Valid formats are `raw`, `mp3`, `opus` and `aac`. `aac` is AAC-LC in an ADTS +stream (`audio/aac`), added by +[RFC-022](rfcs/RFC-022-playback-and-web-client.md); a client that must work +against an older server reads `formats` from `GET /api/v2/transcode/status` +before offering it. Any other value is refused with `400`: unlike the Subsonic +façade, this API does not fall back to a format nobody asked for. Byte ranges +apply to originals and completed cached transcodes. A live transcode uses temporal `offset_ms` and a chunked response; it does not implement arbitrary output-byte ranges. Seeking a live transcode through `offset_ms` abandons the stream that was @@ -611,10 +631,16 @@ See [RFC-007](rfcs/RFC-007-library-event-stream.md) for the reasoning. Transcoding is bounded twice: once for the whole server and once per account. `GET /api/v2/transcode/status` reports both ceilings alongside `active`, so a client can size its own concurrency before asking rather than discovering the -limit by being refused: +limit by being refused. It also lists the `formats` a transcode can produce: ```json -{ "available": true, "active": 1, "global_limit": 4, "per_user_limit": 2 } +{ + "available": true, + "active": 1, + "global_limit": 4, + "per_user_limit": 2, + "formats": ["mp3", "opus", "aac"] +} ``` Over either ceiling the stream route answers `429` with `Retry-After`. diff --git a/docs/rfcs/RFC-022-playback-and-web-client.md b/docs/rfcs/RFC-022-playback-and-web-client.md new file mode 100644 index 00000000..0f266667 --- /dev/null +++ b/docs/rfcs/RFC-022-playback-and-web-client.md @@ -0,0 +1,263 @@ +# RFC-022 — Playback and the web client: loudness, discography, install, AAC + +- **Status**: Proposed +- **Implemented by**: [#326](https://github.com/InstaZDLL/waveflow-server/pull/326) + (this RFC, and all seven decisions with it). The *Status* field above never + flips in this project; this line is the one that says what is running. +- **Date**: 2026-10-25 +- **Authors**: the WaveFlow project +- **Depends on**: [RFC-002](RFC-002-waveflow-server-v2.md) (the stream tickets, + the frozen Subsonic contract, the one set of domain services) + +## Problem + +A comparative audit of this server against Navidrome, Meelo, kima-hub and +Ampache (2026-10-24) listed five gaps in how music reaches a listener. Each +was checked in the code before this was written, and two of them turned out +smaller or different from what the audit said: + +1. **ReplayGain is read and never applied.** The scanner stores the four + ReplayGain values and `SongItem` carries them on `/api/v2`, but the web + client's `Song` type does not even declare them, and the player plays every + track at the level its master was cut at. +2. **An artist page is one flat grid.** `album.release_types` is stored, and — + contrary to the audit — it *is* on the native wire already: `AlbumItem` + serialises it on every album listing. What is missing is a decision about + what the values mean, so the page cannot group by them, and nothing lists + the albums an artist is credited on without being their album artist. +3. **The web client cannot be installed.** `webapp/public` has no manifest and + no icon a phone accepts, so "Add to home screen" makes a bookmark. +4. **Transcoding knows two formats, and refuses the rest.** `src/media.rs` + encodes MP3 and Opus only. A Subsonic `format=aac` or `format=flac` is + answered error 10, where every other server plays something. +5. **A Subsonic form POST is capped at 64 KiB.** The audit estimated the cap + at about 1,600 UUIDs. It is lower, and it depends on the name of the + parameter: `songId=&` is 44 bytes, so `createPlaylist` stops short of + 1,490 tracks; `songIdToAdd=&` is 49, so `updatePlaylist` stops short + of 1,338, less in both cases what the credentials take. A test sending the + playlist ceiling of 10,000 in one `updatePlaylist` was answered + `Invalid form body` under the old bound. `savePlayQueue` is a + different case: it is refused above 400 tracks by `MAX_QUEUE_TRACKS`, a + domain bound shared with the native API, long before the body is. + +## Decision 1 — ReplayGain in the web player, through Web Audio + +The `