Documents every currently-registered OpenSubsonic extension (apiKeyAuthentication, formPost, getPodcastEpisode, songLyrics, indexBasedQueue, playbackReport, topSongsByArtistId, transcodeOffset, transcoding, sonicSimilarity), what backs each one, and what it's tested against — cross-referencing the extensions-registry regression guard that fails the build if an advertised extension's handler ever goes missing. Also updates the getArtistInfo/getAlbumInfo/ getSimilarSongs rows and error-code list for Phase 3 changes. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_014r98AtcBbevpscWLsfwVvv
331 lines
22 KiB
Markdown
331 lines
22 KiB
Markdown
# Subsonic API compatibility
|
||
|
||
VibeSonic implements the **complete original Subsonic REST API through
|
||
version 1.16.1** (Phase 2) plus a growing set of **OpenSubsonic
|
||
extensions** (Phase 3). Every endpoint below is backed by real data
|
||
from VibeSonic's own database/scanner/streaming/transcoding/jukebox/
|
||
audio-analysis subsystems — none of them are stub handlers that return
|
||
an empty successful response. `getOpenSubsonicExtensions` only ever
|
||
advertises an extension once its handler and tests exist and pass; see
|
||
[OpenSubsonic extensions](#opensubsonic-extensions) below for the full
|
||
list and what backs each one.
|
||
|
||
## How to read this table
|
||
|
||
- **Version** — the Subsonic API version the endpoint was introduced in
|
||
(per the official protocol history), included so client compatibility
|
||
checks make sense.
|
||
- **Implemented** — ✅ real, ⚠️ real but with a noted simplification.
|
||
- **XML / JSON Tested** — both formats share one Go struct tree
|
||
(`internal/subsonic/model*.go`) serialized via `encoding/xml` and
|
||
`encoding/json` respectively, so a test that decodes one and asserts
|
||
on response shape effectively covers both; most endpoint tests
|
||
exercise JSON responses directly and XML responses via the shared
|
||
`decodeXML`/`decodeJSON` test helpers in `internal/subsonic/*_test.go`.
|
||
- **Auth Tested** — covered by `internal/subsonic/router_test.go`
|
||
(wrong password, missing credentials, disabled user, token auth,
|
||
`enc:` password auth, external-auth-only accounts, protocol version
|
||
negotiation) plus per-endpoint auth-failure cases where relevant.
|
||
- **Permission Tested** — covered where the endpoint enforces a specific
|
||
Subsonic role (stream/download/podcast/jukebox/admin/ownership).
|
||
|
||
## System
|
||
|
||
| Endpoint | Version | Implemented | XML Tested | JSON Tested | Auth Tested | Permission Tested | Notes |
|
||
|---|---|---|---|---|---|---|---|
|
||
| `ping` | 1.0.0 | ✅ | ✅ | ✅ | ✅ | — | |
|
||
| `getLicense` | 1.0.0 | ✅ | ✅ | ✅ | ✅ | — | Always reports a valid, unlimited license — VibeSonic never requires one. |
|
||
|
||
## Music folders
|
||
|
||
| Endpoint | Version | Implemented | XML Tested | JSON Tested | Auth Tested | Permission Tested | Notes |
|
||
|---|---|---|---|---|---|---|---|
|
||
| `getMusicFolders` | 1.0.0 | ✅ | ✅ | ✅ | ✅ | ✅ | Respects per-user folder restrictions; folder IDs are small sequential integers derived from creation order (the protocol's `musicFolder.id` is `xs:int`), mapped internally onto VibeSonic's opaque folder IDs. |
|
||
|
||
## Browsing
|
||
|
||
| Endpoint | Version | Implemented | XML Tested | JSON Tested | Auth Tested | Permission Tested | Notes |
|
||
|---|---|---|---|---|---|---|---|
|
||
| `getIndexes` | 1.0.0 | ✅ | ✅ | — | ✅ | ✅ (folder restriction) | Filesystem-style browsing, driven entirely by `media_files.relative_path` — never faked from the ID3 model. |
|
||
| `getMusicDirectory` | 1.0.0 | ✅ | ✅ | — | ✅ | ✅ (folder restriction) | Directory IDs encode (folder, path); rejects a request for a folder the user can't see. |
|
||
| `getGenres` | 1.9.0 | ✅ | — | ✅ | ✅ | — | Real per-genre song/album counts. |
|
||
| `getArtists` | 1.8.0 | ✅ | — | ✅ | ✅ | — | ID3 browsing; only artists with at least one album, per protocol convention. |
|
||
| `getArtist` | 1.8.0 | ✅ | — | ✅ | ✅ | — | |
|
||
| `getAlbum` | 1.8.0 | ✅ | ✅ | ✅ | ✅ | ✅ (folder restriction on songs) | |
|
||
| `getSong` | 1.8.0 | ✅ | — | ✅ | ✅ | ✅ (folder restriction) | |
|
||
| `getVideos` | 1.8.0 | ✅ | — | ✅ | — | — | Real: lists media files the scanner tagged `kind=video`. |
|
||
| `getVideoInfo` | 1.14.0 | ⚠️ | — | — | — | — | Reports one default audio track (VibeSonic doesn't probe multi-audio-track containers yet) and real sidecar `.srt`/`.vtt`/`.ass` captions found next to the file. |
|
||
| `getArtistInfo` | 1.11.0 | ✅ | — | ✅ | — | — | Local MusicBrainz ID always returned; biography (MusicBrainz disambiguation) filled in only when `VIBESONIC_ENABLE_EXTERNAL_METADATA=true` and the artist has a tagged MBID — see [OpenSubsonic extensions](#opensubsonic-extensions). |
|
||
| `getArtistInfo2` | 1.11.0 | ✅ | — | ✅ | — | — | Same as above, ID3 form. |
|
||
| `getAlbumInfo` | 1.14.0 | ✅ | — | ✅ | — | — | Local MusicBrainz ID always returned; cover-art image URLs filled in from the Cover Art Archive under the same conditions as `getArtistInfo`. |
|
||
| `getAlbumInfo2` | 1.14.0 | ✅ | — | ✅ | — | — | Same. |
|
||
| `getSimilarSongs` | 1.11.0 | ✅ | — | — | — | — | Genre-overlap candidate set (song/album/artist seed), re-ranked by real sonic-feature distance when the seed song has been audio-analyzed. Not the same as `getSonicSimilarTracks` (pure sonic search) — see below. |
|
||
| `getSimilarSongs2` | 1.11.0 | ✅ | — | — | — | — | ID3 (artist-seeded) form. |
|
||
| `getTopSongs` | 1.13.0 | ✅ | — | ✅ | — | — | Ranked by real aggregate play count across all users. |
|
||
|
||
## Album/song lists
|
||
|
||
| Endpoint | Version | Implemented | XML Tested | JSON Tested | Auth Tested | Permission Tested | Notes |
|
||
|---|---|---|---|---|---|---|---|
|
||
| `getAlbumList` | 1.2.0 | ✅ | — | ✅ | ✅ | — | All 10 official types: random, newest, highest, frequent, recent, alphabeticalByName, alphabeticalByArtist, starred, byYear, byGenre. |
|
||
| `getAlbumList2` | 1.8.0 | ✅ | ✅ | ✅ | ✅ | — | Same types, ID3 form. |
|
||
| `getRandomSongs` | 1.2.0 | ✅ | — | ✅ | — | — | Supports `size`, `genre`, `fromYear`, `toYear`. |
|
||
| `getSongsByGenre` | 1.9.0 | ✅ | — | ✅ | — | — | Supports `count`/`offset`. |
|
||
| `getNowPlaying` | 1.0.0 | ✅ | — | ✅ | — | — | Real `now_playing` table, populated by `scrobble` (submission=false) and native web streaming. |
|
||
| `getStarred` | 1.8.0 | ✅ | — | ✅ | — | — | |
|
||
| `getStarred2` | 1.8.0 | ✅ | — | ✅ | — | — | |
|
||
|
||
## Search
|
||
|
||
| Endpoint | Version | Implemented | XML Tested | JSON Tested | Auth Tested | Permission Tested | Notes |
|
||
|---|---|---|---|---|---|---|---|
|
||
| `search` | 1.0.0 (deprecated 1.4.0) | ✅ | — | ✅ | — | — | Still fully functional, as required — deprecated doesn't mean removed. |
|
||
| `search2` | 1.4.0 | ✅ | — | ✅ | — | — | Independent offset/count per artist/album/song. |
|
||
| `search3` | 1.8.0 | ✅ | — | ✅ | ✅ | — | ID3 form; independent offset/count per category. |
|
||
|
||
## Playlists
|
||
|
||
| Endpoint | Version | Implemented | XML Tested | JSON Tested | Auth Tested | Permission Tested | Notes |
|
||
|---|---|---|---|---|---|---|---|
|
||
| `getPlaylists` | 1.0.0 | ✅ | — | ✅ | — | — | Own + public playlists; `username` param for admins to view another user's. |
|
||
| `getPlaylist` | 1.0.0 | ✅ | — | ✅ | — | ✅ (ownership/public) | |
|
||
| `createPlaylist` | 1.0.0 | ✅ | — | ✅ | — | ✅ (playlist role) | Supports both "new playlist" and "replace an existing playlist's songs" forms. |
|
||
| `updatePlaylist` | 1.8.0 | ✅ | — | — | — | ✅ (ownership) | Rename, re-describe, publicity toggle, `songIndexToRemove`/`songIdToAdd`. |
|
||
| `deletePlaylist` | 1.0.0 | ✅ | — | — | — | ✅ (ownership) | |
|
||
|
||
## Media retrieval
|
||
|
||
| Endpoint | Version | Implemented | XML Tested | JSON Tested | Auth Tested | Permission Tested | Notes |
|
||
|---|---|---|---|---|---|---|---|
|
||
| `stream` | 1.0.0 | ✅ | n/a (binary) | n/a | ✅ | ✅ (stream role) | Direct streaming (range requests, no buffering) or real ffmpeg transcoding depending on `format`/`maxBitRate`; folder-permission checked. |
|
||
| `download` | 1.0.0 | ✅ | n/a | n/a | — | ✅ (download role) | Original bytes with `Content-Disposition: attachment`. |
|
||
| `hls` | 1.8.0 | ✅ | n/a | n/a | — | ✅ (stream role) | Real ffmpeg HLS segmenting, cached per (source, bitrate); segment URLs re-embed the caller's auth params. |
|
||
| `getCaptions` | 1.14.0 | ✅ | n/a | n/a | — | — | Serves real sidecar subtitle files. |
|
||
| `getCoverArt` | 1.0.0 | ✅ | n/a | n/a | — | ✅ (coverArt role) | Embedded or folder art, resized and cached on disk. |
|
||
| `getLyrics` | 1.2.0 | ✅ | ✅ | — | — | — | Title/artist search over the same structured lyrics storage `getLyricsBySongId` reads; empty (not fabricated) if none found. |
|
||
| `getAvatar` | 1.8.0 | ⚠️ | — | — | — | — | Honestly 404s — VibeSonic has no user-avatar feature in Phase 1/2. |
|
||
|
||
## Transcoding
|
||
|
||
Integrated into `stream`/`download`/`hls` above rather than a separate
|
||
endpoint (the classic API has none). `internal/transcoding` runs real
|
||
ffmpeg processes, bounded to 4 concurrent jobs, piping output directly to
|
||
the response (never buffering a full file), and is killed when the
|
||
client disconnects (context cancellation). Supported output formats:
|
||
`mp3`, `opus`, `aac`. `maxBitRate`, `format`, and `timeOffset` are all
|
||
honored.
|
||
|
||
## Annotation
|
||
|
||
| Endpoint | Version | Implemented | XML Tested | JSON Tested | Auth Tested | Permission Tested | Notes |
|
||
|---|---|---|---|---|---|---|---|
|
||
| `star` | 1.4.0 | ✅ | ✅ | — | — | — | Song/album/artist, multiple IDs per call. |
|
||
| `unstar` | 1.4.0 | ✅ | ✅ | — | — | — | |
|
||
| `setRating` | 1.6.0 | ✅ | ✅ | — | — | — | Auto-detects song/album/artist from the given ID. |
|
||
| `scrobble` | 1.5.0 | ✅ | ✅ | — | — | — | `submission=true` records playback history + a scrobble row; `false` only updates now-playing. |
|
||
|
||
## Sharing
|
||
|
||
| Endpoint | Version | Implemented | XML Tested | JSON Tested | Auth Tested | Permission Tested | Notes |
|
||
|---|---|---|---|---|---|---|---|
|
||
| `getShares` | 1.6.0 | ✅ | — | ✅ | — | ✅ (share role) | Own shares, or all shares for an admin. |
|
||
| `createShare` | 1.6.0 | ✅ | — | ✅ | — | ✅ (share role) | Songs and/or whole albums; real public URL. |
|
||
| `updateShare` | 1.6.0 | ✅ | — | — | — | ✅ (ownership) | |
|
||
| `deleteShare` | 1.6.0 | ✅ | — | — | — | ✅ (ownership) | |
|
||
|
||
A real public, unauthenticated share page/stream route also exists at
|
||
`/share/{token}` and `/share/{token}/stream/{songID}` — see
|
||
[Web UI](#web-ui). Streaming through a share is still path-validated the
|
||
same way every other stream is; a share can never expose an arbitrary
|
||
filesystem path.
|
||
|
||
## Podcasts
|
||
|
||
| Endpoint | Version | Implemented | XML Tested | JSON Tested | Auth Tested | Permission Tested | Notes |
|
||
|---|---|---|---|---|---|---|---|
|
||
| `getPodcasts` | 1.6.0 | ✅ | — | ✅ | — | ✅ (podcast role) | |
|
||
| `getNewestPodcasts` | 1.13.0 | ✅ | — | ✅ | — | ✅ (podcast role) | |
|
||
| `refreshPodcasts` | 1.9.0 | ✅ | — | — | — | ✅ (admin) | Real RSS fetch + parse for every channel, in the background. |
|
||
| `createPodcastChannel` | 1.9.0 | ✅ | — | — | — | ✅ (admin) | Kicks off a real feed refresh. |
|
||
| `deletePodcastChannel` | 1.9.0 | ✅ | — | — | — | ✅ (admin) | |
|
||
| `deletePodcastEpisode` | 1.9.0 | ✅ | — | — | — | ✅ (admin) | |
|
||
| `downloadPodcastEpisode` | 1.9.0 | ✅ | — | — | — | ✅ (admin) | Real bounded background download; the result becomes an ordinary streamable song. Interrupted downloads recover to "new" on restart (`internal/podcasts.Manager.RecoverInterruptedDownloads`, run at startup). |
|
||
|
||
`internal/podcasts` is a real subsystem: RSS/Atom parsing
|
||
(`internal/podcasts/rss.go`), episode metadata (GUID-keyed, stable
|
||
across refreshes), enclosure downloads bounded to 2 concurrent jobs, and
|
||
downloaded episodes registered as normal `media_files`/`songs` rows so
|
||
they stream through the exact same path as scanned music.
|
||
|
||
## Jukebox
|
||
|
||
| Endpoint | Version | Implemented | XML Tested | JSON Tested | Auth Tested | Permission Tested | Notes |
|
||
|---|---|---|---|---|---|---|---|
|
||
| `jukeboxControl` | 1.2.0 (current form 1.7.0) | ✅ | ✅ | ✅ | — | ✅ (jukebox role) | Every action: get, status, set, start, stop, skip, add, clear, remove, shuffle, setGain. |
|
||
|
||
Backed by a real `mpv` process controlled over its JSON IPC socket
|
||
(`internal/jukebox`), tested against an actual `mpv` binary (playback,
|
||
pause/resume, add/remove/clear, gain, shuffle, index seeking all
|
||
verified in `internal/jukebox/jukebox_test.go`). If `mpv` isn't
|
||
installed, the jukebox is administratively unavailable and
|
||
`jukeboxControl` returns a clear error — never fake success.
|
||
|
||
## Internet radio
|
||
|
||
| Endpoint | Version | Implemented | XML Tested | JSON Tested | Auth Tested | Permission Tested | Notes |
|
||
|---|---|---|---|---|---|---|---|
|
||
| `getInternetRadioStations` | 1.9.0 | ✅ | — | ✅ | — | — | |
|
||
| `createInternetRadioStation` | 1.16.0 | ✅ | ✅ | — | — | ✅ (admin) | |
|
||
| `updateInternetRadioStation` | 1.16.0 | ✅ | — | — | — | ✅ (admin) | |
|
||
| `deleteInternetRadioStation` | 1.16.0 | ✅ | ✅ | — | — | ✅ (admin) | |
|
||
|
||
Also exposed in the web UI at `/radio` (viewing: any signed-in user;
|
||
managing: admin).
|
||
|
||
## Chat
|
||
|
||
| Endpoint | Version | Implemented | XML Tested | JSON Tested | Auth Tested | Permission Tested | Notes |
|
||
|---|---|---|---|---|---|---|---|
|
||
| `getChatMessages` | 1.2.0 | ✅ | — | ✅ | — | — | Supports `since`. |
|
||
| `addChatMessage` | 1.2.0 | ✅ | — | ✅ | — | — | |
|
||
|
||
## User management
|
||
|
||
| Endpoint | Version | Implemented | XML Tested | JSON Tested | Auth Tested | Permission Tested | Notes |
|
||
|---|---|---|---|---|---|---|---|
|
||
| `getUser` | 1.3.0 | ✅ | — | ✅ | — | ✅ (self or admin) | |
|
||
| `getUsers` | 1.8.0 | ✅ | — | — | — | ✅ (admin) | |
|
||
| `createUser` | 1.1.0 | ✅ | ✅ | — | — | ✅ (admin) | Full role set + `musicFolderId` restriction. |
|
||
| `updateUser` | 1.10.1 | ✅ | ✅ | ✅ | — | ✅ (admin) | Role params not present in the request are preserved, not reset to false (regression-tested). |
|
||
| `deleteUser` | 1.3.0 | ✅ | ✅ | — | — | ✅ (admin; can't delete self) | |
|
||
| `changePassword` | 1.1.0 | ✅ | ✅ | — | — | ✅ (self or admin) | Also refreshes the encrypted copy used for token auth. |
|
||
|
||
The full Subsonic role set (adminRole, settingsRole, streamRole,
|
||
jukeboxRole, downloadRole, uploadRole, playlistRole, coverArtRole,
|
||
commentRole, podcastRole, shareRole, videoConversionRole) is stored per
|
||
user and enforced on the server side by every endpoint above — never
|
||
only hidden in the UI. Per-user music folder restriction is also
|
||
enforced server-side (`internal/subsonic/folders.go`).
|
||
|
||
## Bookmarks
|
||
|
||
| Endpoint | Version | Implemented | XML Tested | JSON Tested | Auth Tested | Permission Tested | Notes |
|
||
|---|---|---|---|---|---|---|---|
|
||
| `getBookmarks` | 1.9.0 | ✅ | — | ✅ | — | — | Per-user. |
|
||
| `createBookmark` | 1.9.0 | ✅ | ✅ | — | — | — | Position (ms) + comment. |
|
||
| `deleteBookmark` | 1.9.0 | ✅ | ✅ | — | — | — | |
|
||
|
||
## Play queue
|
||
|
||
| Endpoint | Version | Implemented | XML Tested | JSON Tested | Auth Tested | Permission Tested | Notes |
|
||
|---|---|---|---|---|---|---|---|
|
||
| `getPlayQueue` | 1.12.0 | ✅ | — | ✅ | — | — | Per-user, persisted. |
|
||
| `savePlayQueue` | 1.12.0 | ✅ | ✅ | ✅ | — | — | |
|
||
|
||
## Scanner
|
||
|
||
| Endpoint | Version | Implemented | XML Tested | JSON Tested | Auth Tested | Permission Tested | Notes |
|
||
|---|---|---|---|---|---|---|---|
|
||
| `getScanStatus` | 1.15.0 | ✅ | — | ✅ | — | — | Real status/count from `internal/scanner`, not simulated. |
|
||
| `startScan` | 1.15.0 | ✅ | — | ✅ | — | ✅ (admin) | |
|
||
|
||
---
|
||
|
||
## OpenSubsonic extensions
|
||
|
||
VibeSonic advertises these via `getOpenSubsonicExtensions`
|
||
(`internal/subsonic/extensions.go`); `TestGetOpenSubsonicExtensionsNeverStubbed`
|
||
in `internal/subsonic/opensubsonic_core_test.go` fails the build if an
|
||
extension is ever registered without its handler actually existing, so
|
||
this list can't drift ahead of what's real.
|
||
|
||
| Extension | Versions | Endpoint(s) | Tested | Notes |
|
||
|---|---|---|---|---|
|
||
| `apiKeyAuthentication` | 1 | `tokenInfo`, `apiKey=` param on every endpoint | ✅ | Per-user named keys with plaintext shown once, SHA-256 hash stored, creation/last-used tracking, revocation (self or admin). `apiKey` conflicting with `u` returns error 43; an invalid/revoked key returns error 44. Managed at Settings → API Keys; admins see/revoke everyone's keys at `/admin/api-keys`. |
|
||
| `formPost` | 1 | (framework-wide) | ✅ | `application/x-www-form-urlencoded` POST supported on every endpoint via `net/http`'s `ParseForm`; a key present in both the POST body and the query string resolves to the **body** value (verified empirically and regression-tested). |
|
||
| `getPodcastEpisode` | 1 | `getPodcastEpisode` | ✅ | Real lookup against the Phase 2 podcast subsystem; 404s honestly for an unknown episode. |
|
||
| `songLyrics` | 1, 2 | `getLyricsBySongId` | ✅ | Structured lyrics (multiple entries per song, multiple languages) from embedded tags and sidecar `.lrc`/Enhanced LRC files, stored with UTF-8 byte offsets computed once at index time. `enhanced=true` additionally returns `cueLine`/`cue` word-level timing — but only for entries actually parsed from a source with real word timing (Enhanced LRC); plain and line-only-synced lyrics never get fabricated cues. |
|
||
| `indexBasedQueue` | 1 | `getPlayQueueByIndex`, `savePlayQueueByIndex` | ✅ | Same `play_queues` storage as the classic queue, but exposes `currentIndex` directly instead of resolving it to a song ID — correct even when the queue repeats the same song at multiple positions (regression-tested). |
|
||
| `playbackReport` | 1 | `reportPlayback` | ✅ | Persists user/media/position/state/client/timestamp (`playback_reports` table); `state=stopped` also clears now-playing and (unless `ignoreScrobble`) logs playback history. Never leaks one user's playback state to another. |
|
||
| `topSongsByArtistId` | 1 | `getTopSongs` (`id` param) | ✅ | `id` (canonical internal artist ID) takes priority over the legacy `artist` name param when both are given. |
|
||
| `transcodeOffset` | 1 | `stream` (`timeOffset` param) | ✅ | Forces a real transcode with an actual ffmpeg `-ss` seek — never decode-from-zero — verified by asserting the response isn't a range-servable direct passthrough. |
|
||
| `transcoding` | 1 | `getTranscodeDecision`, `getTranscodeStream` | ✅ | Real capability matching: compares the source file's actual scanned format/channels against the client's `directPlayProfiles`; falls back to searching `transcodingProfiles` for a codec VibeSonic's ffmpeg pipeline can actually encode (mp3/opus/aac) that also satisfies any `codecProfile` limitations the client marked required. `canDirectPlay`/`canTranscode` are never set true without an actual match. `getTranscodeStream` takes the opaque `transcodeParams` token verbatim and streams real transcoded audio with real seek support. |
|
||
| `sonicSimilarity` | 1 | `getSonicSimilarTracks`, `findSonicPath` | ✅ | Backed by `internal/audiofeatures` (real ffmpeg PCM decode + a hand-written FFT for tempo/loudness/spectral-shape/chroma, computed by the scanner as part of each scan) and `internal/similarity` (an in-process, z-score-normalized nearest-neighbor index — no external vector database). A song that hasn't been analyzed yet simply doesn't appear in results; `findSonicPath` walks real feature-space geometry to a real intermediate song at each hop, never an invented one. |
|
||
|
||
### OpenSubsonic response/model additions
|
||
|
||
- `subsonic-response`: `type`, `serverVersion`, `openSubsonic` — always
|
||
populated (`internal/subsonic/model.go`).
|
||
- `error.helpUrl` — set on API-key-related errors (43, 44), pointing at
|
||
the Settings API Keys page.
|
||
- `Child`/other entity fields VibeSonic already populated correctly
|
||
before Phase 3 (`replayGain*`, `musicBrainzId`, disc/track totals,
|
||
etc.) are unchanged; nothing new was added here beyond what's listed
|
||
in the extension table above (structured lyrics, sonic-match
|
||
entries, transcode-decision/stream-details, index-based play queue).
|
||
|
||
### Optional external metadata (MusicBrainz / Cover Art Archive)
|
||
|
||
Not an OpenSubsonic extension itself, but part of Phase 3's metadata
|
||
work: `getArtistInfo(2)`/`getAlbumInfo(2)` fetch a real MusicBrainz
|
||
artist disambiguation or Cover Art Archive front-cover image when
|
||
`VIBESONIC_ENABLE_EXTERNAL_METADATA=true` **and** the entity already
|
||
carries a locally-tagged MusicBrainz ID, caching results in
|
||
`external_metadata_cache` for 30 days. VibeSonic works completely
|
||
without Internet access: the flag defaults off, and any lookup
|
||
failure/timeout silently falls back to local-only fields rather than
|
||
erroring the request. See `internal/metadataenrich` and
|
||
`internal/subsonic/enrich.go`.
|
||
|
||
## Authentication
|
||
|
||
Both original Subsonic authentication modes are implemented in
|
||
`internal/subsonic/auth.go`:
|
||
|
||
- **Token/salt** (`u`, `t`, `s`): `t = md5(password + salt)`. Verified
|
||
against a separate, AES-256-GCM–encrypted copy of the password
|
||
(`internal/auth/subsonic_secret.go`) kept in sync with every password
|
||
change — never against the Argon2id hash (impossible, by design) and
|
||
never by storing the password in the clear.
|
||
- **Password** (`p`): plain or `enc:<hex>`, verified against the
|
||
Argon2id hash used everywhere else in VibeSonic.
|
||
|
||
An account created purely through external (trusted-proxy) auth has no
|
||
local password and therefore cannot authenticate to `/rest/*` until an
|
||
administrator or the user sets one — `/rest/*` is completely independent
|
||
of the browser session/CSRF/external-auth flow (see
|
||
[`docs/external-auth.md`](external-auth.md)).
|
||
|
||
## Common parameters
|
||
|
||
`u`, `p`, `t`, `s`, `v`, `c`, `f` (xml/json/jsonp), and `callback` are
|
||
all handled centrally in `internal/subsonic/router.go` and
|
||
`internal/subsonic/auth.go` — no handler parses or validates them
|
||
itself. `v` is checked against `ServerVersion` (currently `1.16.1`): a
|
||
client claiming a newer protocol version gets error 20; older clients
|
||
are accepted, since the protocol is additive.
|
||
|
||
## Error codes
|
||
|
||
All defined in `internal/subsonic/errors.go`: 0 (generic), 10 (missing
|
||
parameter), 20 (client must upgrade), 30 (server must upgrade, reserved
|
||
— VibeSonic never needs to require a newer client), 40 (wrong
|
||
credentials), 41 (token auth unsupported for this account), 42 (auth
|
||
mechanism not supported), 43 (conflicting auth mechanisms — e.g. both
|
||
`apiKey` and `u` given), 44 (invalid/revoked API key), 50 (unauthorized
|
||
for the operation), 60 (license/trial — never returned), 70 (not
|
||
found).
|
||
|
||
## Web UI
|
||
|
||
Newly exposed through the Bootstrap UI in Phase 2: Podcasts (`/podcasts`,
|
||
admin at `/admin/podcasts`), Internet Radio (`/radio`), Shares
|
||
(`/shares`, plus the public `/share/{token}` page), Bookmarks
|
||
(`/bookmarks`), per-user Subsonic permission editing
|
||
(`/admin/users/{id}/permissions`), and API key management (Settings →
|
||
API Keys for any user; `/admin/api-keys` for admins to view/revoke
|
||
everyone's). The admin Jukebox page
|
||
(`/admin/jukebox`) shows live status and emergency stop/clear; building
|
||
a jukebox playlist is done from a Subsonic client via `jukeboxControl`,
|
||
which is the actual protocol surface for that feature.
|