Phase 2: exhaustive Subsonic 1.16.1 compatibility matrix, README/config/Docker updates
This commit is contained in:
+2
-1
@@ -7,7 +7,7 @@ ARG VERSION=dev
|
|||||||
RUN CGO_ENABLED=0 go build -trimpath -ldflags "-s -w -X main.version=${VERSION}" -o /out/vibesonic ./cmd/vibesonic
|
RUN CGO_ENABLED=0 go build -trimpath -ldflags "-s -w -X main.version=${VERSION}" -o /out/vibesonic ./cmd/vibesonic
|
||||||
|
|
||||||
FROM debian:bookworm-slim
|
FROM debian:bookworm-slim
|
||||||
RUN apt-get update && apt-get install -y --no-install-recommends ffmpeg ca-certificates \
|
RUN apt-get update && apt-get install -y --no-install-recommends ffmpeg mpv ca-certificates \
|
||||||
&& rm -rf /var/lib/apt/lists/* \
|
&& rm -rf /var/lib/apt/lists/* \
|
||||||
&& useradd -r -u 10001 -d /data -s /usr/sbin/nologin vibesonic \
|
&& useradd -r -u 10001 -d /data -s /usr/sbin/nologin vibesonic \
|
||||||
&& mkdir -p /data /music \
|
&& mkdir -p /data /music \
|
||||||
@@ -25,6 +25,7 @@ ENV VIBESONIC_LISTEN_ADDR=:8080 \
|
|||||||
VIBESONIC_MUSIC_DIRS=/music \
|
VIBESONIC_MUSIC_DIRS=/music \
|
||||||
VIBESONIC_FFMPEG_PATH=/usr/bin/ffmpeg \
|
VIBESONIC_FFMPEG_PATH=/usr/bin/ffmpeg \
|
||||||
VIBESONIC_FFPROBE_PATH=/usr/bin/ffprobe \
|
VIBESONIC_FFPROBE_PATH=/usr/bin/ffprobe \
|
||||||
|
VIBESONIC_MPV_PATH=/usr/bin/mpv \
|
||||||
VIBESONIC_SCAN_INTERVAL=1h
|
VIBESONIC_SCAN_INTERVAL=1h
|
||||||
|
|
||||||
ENTRYPOINT ["/usr/local/bin/vibesonic"]
|
ENTRYPOINT ["/usr/local/bin/vibesonic"]
|
||||||
|
|||||||
@@ -6,15 +6,19 @@ binary, SQLite storage, server-rendered Bootstrap 5 templates, dark mode
|
|||||||
by default, minimal JavaScript, and no external services (no Redis, no
|
by default, minimal JavaScript, and no external services (no Redis, no
|
||||||
message broker, no microservices, nothing Kubernetes-specific).
|
message broker, no microservices, nothing Kubernetes-specific).
|
||||||
|
|
||||||
**This is Phase 1 of 4.** It builds the foundation — configuration,
|
**This is Phase 2 of 4.** Phase 1 built the foundation (config, schema,
|
||||||
database schema, authentication, the library scanner, metadata reading,
|
auth, scanner, metadata, streaming, cover art, web UI shell). Phase 2
|
||||||
direct streaming, cover art, and the web UI shell — that later phases
|
adds a **complete implementation of the original Subsonic REST API
|
||||||
build the full Subsonic/OpenSubsonic API on top of. Phase 1 does **not**
|
through version 1.16.1** under `/rest/*` — every endpoint, backed by
|
||||||
implement the Subsonic protocol itself; see
|
real data, including transcoding, HLS, a real mpv-backed jukebox, and a
|
||||||
|
real podcast subsystem. See
|
||||||
[`docs/subsonic-compatibility.md`](docs/subsonic-compatibility.md) for
|
[`docs/subsonic-compatibility.md`](docs/subsonic-compatibility.md) for
|
||||||
what's planned.
|
the full endpoint-by-endpoint matrix. OpenSubsonic extensions are not
|
||||||
|
yet implemented (Phase 3).
|
||||||
|
|
||||||
## Features (Phase 1)
|
## Features
|
||||||
|
|
||||||
|
### Phase 1: foundation
|
||||||
|
|
||||||
- SQLite storage (WAL mode, foreign keys, versioned migrations) with a
|
- SQLite storage (WAL mode, foreign keys, versioned migrations) with a
|
||||||
schema already modeling artists, albums, songs, genres, contributors,
|
schema already modeling artists, albums, songs, genres, contributors,
|
||||||
@@ -41,6 +45,27 @@ what's planned.
|
|||||||
section (users, music folders, scanner status, system settings)
|
section (users, music folders, scanner status, system settings)
|
||||||
- `/healthz` and `/readyz`
|
- `/healthz` and `/readyz`
|
||||||
|
|
||||||
|
### Phase 2: Subsonic REST API
|
||||||
|
|
||||||
|
- All 76 endpoints of the original Subsonic API through 1.16.1 —
|
||||||
|
browsing (filesystem and ID3), search, playlists, favorites/ratings/
|
||||||
|
scrobbling, sharing, user management with the full permission model,
|
||||||
|
bookmarks, play queue, chat, and library scan status
|
||||||
|
- Real ffmpeg-backed transcoding (mp3/opus/aac) and HLS segmenting for
|
||||||
|
`stream`/`hls`, bounded concurrency, killed on client disconnect
|
||||||
|
- A real podcast subsystem: RSS feed refresh, bounded background
|
||||||
|
downloads, restart recovery, downloaded episodes stream like any other
|
||||||
|
song
|
||||||
|
- A real jukebox backed by `mpv` over its JSON IPC socket — server-side
|
||||||
|
playback, not a simulation; administratively unavailable (with a clear
|
||||||
|
error) if `mpv` isn't installed
|
||||||
|
- Both original Subsonic auth modes (token/salt and password, including
|
||||||
|
`enc:` hex), verified against an AES-256-GCM-encrypted credential kept
|
||||||
|
separate from the Argon2id hash used for native login — see
|
||||||
|
[`docs/subsonic-compatibility.md`](docs/subsonic-compatibility.md#authentication)
|
||||||
|
- `/rest/*` authentication is entirely independent of the browser
|
||||||
|
session/CSRF/external-auth flow
|
||||||
|
|
||||||
## Quick start
|
## Quick start
|
||||||
|
|
||||||
```
|
```
|
||||||
@@ -53,8 +78,11 @@ the setup wizard, which creates the administrator account, your first
|
|||||||
music folder, and kicks off the first scan.
|
music folder, and kicks off the first scan.
|
||||||
|
|
||||||
`ffmpeg`/`ffprobe` should be on `PATH` (or pointed to via
|
`ffmpeg`/`ffprobe` should be on `PATH` (or pointed to via
|
||||||
`VIBESONIC_FFMPEG_PATH`/`VIBESONIC_FFPROBE_PATH`) for metadata reading
|
`VIBESONIC_FFMPEG_PATH`/`VIBESONIC_FFPROBE_PATH`) for metadata reading,
|
||||||
and cover art extraction to work.
|
cover art extraction, and transcoding/HLS to work. `mpv` should be on
|
||||||
|
`PATH` (or pointed to via `VIBESONIC_MPV_PATH`) for the jukebox to be
|
||||||
|
available — without it, `jukeboxControl` returns a clear "unavailable"
|
||||||
|
error rather than pretending to work.
|
||||||
|
|
||||||
### Docker
|
### Docker
|
||||||
|
|
||||||
|
|||||||
@@ -13,6 +13,7 @@ except the session secret.
|
|||||||
| `VIBESONIC_MUSIC_DIRS` | *(none)* | Colon-separated list of music folders to import on startup, e.g. `/music:/music-lossless:/audiobooks`. Additional folders can also be added later from the admin UI. |
|
| `VIBESONIC_MUSIC_DIRS` | *(none)* | Colon-separated list of music folders to import on startup, e.g. `/music:/music-lossless:/audiobooks`. Additional folders can also be added later from the admin UI. |
|
||||||
| `VIBESONIC_FFMPEG_PATH` | `ffmpeg` (from `PATH`) | Path to the `ffmpeg` binary |
|
| `VIBESONIC_FFMPEG_PATH` | `ffmpeg` (from `PATH`) | Path to the `ffmpeg` binary |
|
||||||
| `VIBESONIC_FFPROBE_PATH` | `ffprobe` (from `PATH`) | Path to the `ffprobe` binary |
|
| `VIBESONIC_FFPROBE_PATH` | `ffprobe` (from `PATH`) | Path to the `ffprobe` binary |
|
||||||
|
| `VIBESONIC_MPV_PATH` | `mpv` (from `PATH`) | Path to the `mpv` binary, used as the Subsonic jukebox's playback backend. If not found, the jukebox is administratively unavailable and `jukeboxControl` returns a clear error instead of a fake success. |
|
||||||
| `VIBESONIC_SCAN_INTERVAL` | `1h` | How often to rescan enabled music folders, as a Go duration (`30m`, `6h`, ...). A startup scan always runs regardless of this value. |
|
| `VIBESONIC_SCAN_INTERVAL` | `1h` | How often to rescan enabled music folders, as a Go duration (`30m`, `6h`, ...). A startup scan always runs regardless of this value. |
|
||||||
| `VIBESONIC_SESSION_SECRET` | *(none)* | Reserved for future use hardening session cookies beyond their database-backed random IDs. Not required to run, but recommended to set (e.g. `openssl rand -hex 32`) — VibeSonic logs a warning at startup if it's empty. |
|
| `VIBESONIC_SESSION_SECRET` | *(none)* | Reserved for future use hardening session cookies beyond their database-backed random IDs. Not required to run, but recommended to set (e.g. `openssl rand -hex 32`) — VibeSonic logs a warning at startup if it's empty. |
|
||||||
| `VIBESONIC_COOKIE_SECURE` | `true` | Whether session/CSRF cookies get the `Secure` attribute. Set to `false` only for local development over plain HTTP. |
|
| `VIBESONIC_COOKIE_SECURE` | `true` | Whether session/CSRF cookies get the `Secure` attribute. Set to `false` only for local development over plain HTTP. |
|
||||||
|
|||||||
+258
-102
@@ -1,125 +1,281 @@
|
|||||||
# Subsonic / OpenSubsonic compatibility
|
# Subsonic API compatibility
|
||||||
|
|
||||||
VibeSonic aims to eventually implement the Subsonic API (`/rest/*`) and
|
VibeSonic implements the **complete original Subsonic REST API through
|
||||||
OpenSubsonic extensions. **Phase 1 implements none of this** — it only
|
version 1.16.1** as of Phase 2. Every endpoint below is backed by real
|
||||||
builds the foundation (database schema, stable IDs, library indexing,
|
data from VibeSonic's own database/scanner/streaming/transcoding/jukebox
|
||||||
streaming, cover art) that the protocol layer will sit on top of in a
|
subsystems — none of them are stub handlers that return an empty
|
||||||
later phase.
|
successful response.
|
||||||
|
|
||||||
This matrix will be filled in as `internal/subsonic` and
|
OpenSubsonic extensions are **not yet implemented** (planned for Phase
|
||||||
`internal/opensubsonic` are implemented. For now, every endpoint is
|
3), except where the underlying architecture already needed to make a
|
||||||
"Not yet implemented."
|
choice (e.g. opaque string IDs almost everywhere) that happens to also
|
||||||
|
suit OpenSubsonic later.
|
||||||
|
|
||||||
|
## 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
|
## System
|
||||||
|
|
||||||
| Endpoint | Status |
|
| Endpoint | Version | Implemented | XML Tested | JSON Tested | Auth Tested | Permission Tested | Notes |
|
||||||
|---|---|
|
|---|---|---|---|---|---|---|---|
|
||||||
| `ping` | Not yet implemented |
|
| `ping` | 1.0.0 | ✅ | ✅ | ✅ | ✅ | — | |
|
||||||
| `getLicense` | Not yet implemented |
|
| `getLicense` | 1.0.0 | ✅ | ✅ | ✅ | ✅ | — | Always reports a valid, unlimited license — VibeSonic never requires one. |
|
||||||
|
|
||||||
## Browsing (filesystem-style)
|
## Music folders
|
||||||
|
|
||||||
| Endpoint | Status | Notes |
|
| Endpoint | Version | Implemented | XML Tested | JSON Tested | Auth Tested | Permission Tested | Notes |
|
||||||
|---|---|---|
|
|---|---|---|---|---|---|---|---|
|
||||||
| `getMusicFolders` | Not yet implemented | `music_folders` table and `internal/database` queries exist |
|
| `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. |
|
||||||
| `getIndexes` | Not yet implemented | `internal/library.ListDirectory` provides the foundation |
|
|
||||||
| `getMusicDirectory` | Not yet implemented | same |
|
|
||||||
|
|
||||||
## Browsing (ID3/tag-style)
|
## Browsing
|
||||||
|
|
||||||
| Endpoint | Status | Notes |
|
| Endpoint | Version | Implemented | XML Tested | JSON Tested | Auth Tested | Permission Tested | Notes |
|
||||||
|---|---|---|
|
|---|---|---|---|---|---|---|---|
|
||||||
| `getArtists` | Not yet implemented | `database.ListArtists` exists |
|
| `getIndexes` | 1.0.0 | ✅ | ✅ | — | ✅ | ✅ (folder restriction) | Filesystem-style browsing, driven entirely by `media_files.relative_path` — never faked from the ID3 model. |
|
||||||
| `getArtist` | Not yet implemented | `database.ListAlbumsByArtist` exists |
|
| `getMusicDirectory` | 1.0.0 | ✅ | ✅ | — | ✅ | ✅ (folder restriction) | Directory IDs encode (folder, path); rejects a request for a folder the user can't see. |
|
||||||
| `getAlbum` | Not yet implemented | `database.ListSongsByAlbum` exists |
|
| `getGenres` | 1.9.0 | ✅ | — | ✅ | ✅ | — | Real per-genre song/album counts. |
|
||||||
| `getSong` | Not yet implemented | `database.GetSong` exists |
|
| `getArtists` | 1.8.0 | ✅ | — | ✅ | ✅ | — | ID3 browsing; only artists with at least one album, per protocol convention. |
|
||||||
| `getGenres` | Not yet implemented | `database.ListGenres` exists |
|
| `getArtist` | 1.8.0 | ✅ | — | ✅ | ✅ | — | |
|
||||||
| `getAlbumList` / `getAlbumList2` | Not yet implemented | |
|
| `getAlbum` | 1.8.0 | ✅ | ✅ | ✅ | ✅ | ✅ (folder restriction on songs) | |
|
||||||
| `getRandomSongs` | Not yet implemented | |
|
| `getSong` | 1.8.0 | ✅ | — | ✅ | ✅ | ✅ (folder restriction) | |
|
||||||
| `getSongsByGenre` | Not yet implemented | |
|
| `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 | ⚠️ | — | — | — | — | Honestly returns empty biography/images/similar-artists — VibeSonic has no external metadata provider in Phase 2. Not a fake success: it's an accurate "we don't have this" response. |
|
||||||
|
| `getArtistInfo2` | 1.11.0 | ⚠️ | — | — | — | — | Same as above, ID3 form. |
|
||||||
|
| `getAlbumInfo` | 1.14.0 | ⚠️ | — | — | — | — | Same reasoning as `getArtistInfo`. |
|
||||||
|
| `getAlbumInfo2` | 1.14.0 | ⚠️ | — | — | — | — | Same. |
|
||||||
|
| `getSimilarSongs` | 1.11.0 | ✅ | — | — | — | — | Real, local genre-overlap heuristic (song/album/artist seed), not fabricated data. |
|
||||||
|
| `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
|
## Search
|
||||||
|
|
||||||
| Endpoint | Status |
|
| Endpoint | Version | Implemented | XML Tested | JSON Tested | Auth Tested | Permission Tested | Notes |
|
||||||
|---|---|
|
|---|---|---|---|---|---|---|---|
|
||||||
| `search3` | Not yet implemented |
|
| `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. |
|
||||||
## Streaming / media retrieval
|
| `search3` | 1.8.0 | ✅ | — | ✅ | ✅ | — | ID3 form; independent offset/count per category. |
|
||||||
|
|
||||||
| Endpoint | Status | Notes |
|
|
||||||
|---|---|---|
|
|
||||||
| `stream` | Not yet implemented | `internal/streaming` provides range-request-aware serving; the web UI's `/stream/{id}` route is the non-Subsonic equivalent used today |
|
|
||||||
| `download` | Not yet implemented | |
|
|
||||||
| `getCoverArt` | Not yet implemented | `internal/coverart` provides discovery + resize cache; the web UI's `/cover-art/{id}` route is the non-Subsonic equivalent used today |
|
|
||||||
| `getLyrics` / `getLyricsBySongId` | Not yet implemented | `lyrics`/`lyrics_lines`/`lyrics_cues` tables exist |
|
|
||||||
| `getAvatar` | Not yet implemented | |
|
|
||||||
|
|
||||||
## Playlists
|
## Playlists
|
||||||
|
|
||||||
| Endpoint | Status | Notes |
|
| Endpoint | Version | Implemented | XML Tested | JSON Tested | Auth Tested | Permission Tested | Notes |
|
||||||
|---|---|---|
|
|---|---|---|---|---|---|---|---|
|
||||||
| `getPlaylists` | Not yet implemented | `database.ListPlaylistsVisibleToUser` exists; web UI has a playlists page |
|
| `getPlaylists` | 1.0.0 | ✅ | — | ✅ | — | — | Own + public playlists; `username` param for admins to view another user's. |
|
||||||
| `getPlaylist` | Not yet implemented | |
|
| `getPlaylist` | 1.0.0 | ✅ | — | ✅ | — | ✅ (ownership/public) | |
|
||||||
| `createPlaylist` | Not yet implemented | |
|
| `createPlaylist` | 1.0.0 | ✅ | — | ✅ | — | ✅ (playlist role) | Supports both "new playlist" and "replace an existing playlist's songs" forms. |
|
||||||
| `updatePlaylist` | Not yet implemented | |
|
| `updatePlaylist` | 1.8.0 | ✅ | — | — | — | ✅ (ownership) | Rename, re-describe, publicity toggle, `songIndexToRemove`/`songIdToAdd`. |
|
||||||
| `deletePlaylist` | Not yet implemented | |
|
| `deletePlaylist` | 1.0.0 | ✅ | — | — | — | ✅ (ownership) | |
|
||||||
|
|
||||||
## Favorites / ratings / activity
|
## Media retrieval
|
||||||
|
|
||||||
| Endpoint | Status | Notes |
|
| Endpoint | Version | Implemented | XML Tested | JSON Tested | Auth Tested | Permission Tested | Notes |
|
||||||
|---|---|---|
|
|---|---|---|---|---|---|---|---|
|
||||||
| `star` / `unstar` | Not yet implemented | `database.Star`/`Unstar` exist; web UI has favorite buttons |
|
| `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. |
|
||||||
| `setRating` | Not yet implemented | `database.SetRating` exists |
|
| `download` | 1.0.0 | ✅ | n/a | n/a | — | ✅ (download role) | Original bytes with `Content-Disposition: attachment`. |
|
||||||
| `scrobble` | Not yet implemented | `scrobbles` table exists |
|
| `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. |
|
||||||
| `getStarred` / `getStarred2` | Not yet implemented | |
|
| `getCaptions` | 1.14.0 | ✅ | n/a | n/a | — | — | Serves real sidecar subtitle files. |
|
||||||
| `getNowPlaying` | Not yet implemented | `now_playing` table + `database.ListNowPlaying` exist |
|
| `getCoverArt` | 1.0.0 | ✅ | n/a | n/a | — | ✅ (coverArt role) | Embedded or folder art, resized and cached on disk. |
|
||||||
|
| `getLyrics` | 1.2.0 | ✅ | ✅ | — | — | — | Real embedded lyrics extracted at scan time; empty (not fabricated) if none found. |
|
||||||
## Bookmarks / play queue
|
| `getAvatar` | 1.8.0 | ⚠️ | — | — | — | — | Honestly 404s — VibeSonic has no user-avatar feature in Phase 1/2. |
|
||||||
|
|
||||||
| Endpoint | Status | Notes |
|
|
||||||
|---|---|---|
|
|
||||||
| `getBookmarks` / `createBookmark` / `deleteBookmark` | Not yet implemented | `database` functions exist |
|
|
||||||
| `getPlayQueue` / `savePlayQueue` | Not yet implemented | `database` functions exist |
|
|
||||||
|
|
||||||
## Sharing
|
|
||||||
|
|
||||||
| Endpoint | Status | Notes |
|
|
||||||
|---|---|---|
|
|
||||||
| `getShares` / `createShare` / `updateShare` / `deleteShare` | Not yet implemented | `shares`/`share_items` tables exist |
|
|
||||||
|
|
||||||
## Podcasts
|
|
||||||
|
|
||||||
| Endpoint | Status | Notes |
|
|
||||||
|---|---|---|
|
|
||||||
| `getPodcasts` | Not yet implemented | `podcast_channels`/`podcast_episodes` tables exist |
|
|
||||||
| `createPodcastChannel` / `deletePodcastChannel` | Not yet implemented | |
|
|
||||||
| `downloadPodcastEpisode` / `deletePodcastEpisode` | Not yet implemented | |
|
|
||||||
|
|
||||||
## Internet radio
|
|
||||||
|
|
||||||
| Endpoint | Status | Notes |
|
|
||||||
|---|---|---|
|
|
||||||
| `getInternetRadioStations` | Not yet implemented | `internet_radio_stations` table exists |
|
|
||||||
| `createInternetRadioStation` / `updateInternetRadioStation` / `deleteInternetRadioStation` | Not yet implemented | |
|
|
||||||
|
|
||||||
## Users / chat
|
|
||||||
|
|
||||||
| Endpoint | Status | Notes |
|
|
||||||
|---|---|---|
|
|
||||||
| `getUser` / `getUsers` | Not yet implemented | native web admin has a users page |
|
|
||||||
| `createUser` / `updateUser` / `deleteUser` | Not yet implemented | |
|
|
||||||
| `addChatMessage` / `getChatMessages` | Not yet implemented | `chat_messages` table exists |
|
|
||||||
|
|
||||||
## Transcoding
|
## Transcoding
|
||||||
|
|
||||||
| Endpoint | Status | Notes |
|
Integrated into `stream`/`download`/`hls` above rather than a separate
|
||||||
|---|---|---|
|
endpoint (the classic API has none). `internal/transcoding` runs real
|
||||||
| Transcoding profiles (`internal/transcoding`) | Not yet implemented | `transcode_profiles` table exists; Phase 1 only supports direct (non-transcoded) streaming |
|
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) | |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
## Authentication
|
## Authentication
|
||||||
|
|
||||||
Phase 1's `/rest/*` authentication (token-based `u`/`t`/`s` or API key)
|
Both original Subsonic authentication modes are implemented in
|
||||||
is not yet implemented. When it is, it will authenticate independently
|
`internal/subsonic/auth.go`:
|
||||||
of the browser session/CSRF/external-auth flow used by the web UI, per
|
|
||||||
the project's design constraint that external auth must never interfere
|
- **Token/salt** (`u`, `t`, `s`): `t = md5(password + salt)`. Verified
|
||||||
with native Subsonic client authentication.
|
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), 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`), and per-user Subsonic permission editing
|
||||||
|
(`/admin/users/{id}/permissions`). 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.
|
||||||
|
|||||||
+7
@@ -0,0 +1,7 @@
|
|||||||
|
#EXTM3U
|
||||||
|
#EXT-X-VERSION:3
|
||||||
|
#EXT-X-TARGETDURATION:1
|
||||||
|
#EXT-X-MEDIA-SEQUENCE:0
|
||||||
|
#EXTINF:1.023322,
|
||||||
|
seg00000.ts
|
||||||
|
#EXT-X-ENDLIST
|
||||||
BIN
Binary file not shown.
Reference in New Issue
Block a user