readme: what music does, what it needs, and what it does not do yet
This commit is contained in:
@@ -0,0 +1,145 @@
|
||||
# Music
|
||||
|
||||
The music library for an Officer server: browse `~/Music`, play it in the browser, and serve a synced
|
||||
index to phone and tablet apps.
|
||||
|
||||
Music assumes you already have a library on disk — folders of files you organised yourself — and does
|
||||
not try to become the place you organise it. It indexes what is there, compresses the covers, reads the
|
||||
tags and lyrics, and serves the result over HTTP. The `/music` screen is one client of that API; the
|
||||
mobile apps are another. Nothing is imported, moved or renamed, and removing the plugin leaves the
|
||||
library exactly as it was.
|
||||
|
||||
## What you get
|
||||
|
||||
- **A library screen** at `/music` — a drill-down list on the left, the detail view on the right. An
|
||||
album shows its tracklist, an artist shows its albums grouped by release type, anything else shows a
|
||||
folder grid. Every location is a link, so `?path=` is the address of what you are looking at and
|
||||
cmd-click works.
|
||||
- **Gapless playback in the browser.** Web Audio rather than an `<audio>` element: the next track is
|
||||
decoded ahead of time and scheduled to start at the sample the current one ends, so a continuous mix
|
||||
has no seam on auto-advance.
|
||||
- **Byte-range streaming** with `X-Audio-Duration` on the response, so a player knows the length of a
|
||||
VBR MP3 without scanning it.
|
||||
- **Lyrics**, synced or plain, in a split pane beside the tracklist. Sources in precedence order: an
|
||||
external `<track>.lrc`, an external `<track>.txt`, then embedded tags. A `.txt` containing `[mm:ss]`
|
||||
lines is served as synced.
|
||||
- **Favourites** — tracks, albums and artists — per account, with their own view.
|
||||
- **Resume.** The current track and position are saved per account and per device, so a reload lands
|
||||
back where you were, paused.
|
||||
- **A server-built index for mobile apps.** One manifest call returns every album with a version stamp;
|
||||
a client re-fetches only the albums whose stamp changed. The full contract is [`MUSIC_API.md`](./MUSIC_API.md).
|
||||
- **Playlists**, per account, ordered, over HTTP. There is no playlist UI in the web panels yet — see
|
||||
the limitations.
|
||||
|
||||
## Requirements
|
||||
|
||||
- An Officer server, version 1.0.0 or later.
|
||||
- `ffprobe` and `ffmpeg` on the host. Both come from the `ffmpeg` package on apt, pacman, dnf and brew.
|
||||
The installer probes for them and offers to install the package if either is missing.
|
||||
- Music under **`~/Music`** in the server owner's home. The root is not configurable.
|
||||
|
||||
Without `ffprobe` the indexer still completes and reports success, but every track carries only its
|
||||
filename — no title, artist, album, duration or embedded lyrics. Without `ffmpeg` you lose cover
|
||||
compression and video poster frames. This is why the two are declared separately: they fail differently
|
||||
and you should be told which one you are missing.
|
||||
|
||||
## Install
|
||||
|
||||
From the Officer marketplace: find Music, install. That clones the repo into `plugins/music`, checks
|
||||
the host binaries, applies the four database tables, mounts `/api/music` and `/music`, and starts the
|
||||
`officer-music` sidecar under PM2.
|
||||
|
||||
Uninstalling removes the route and the process. **It never drops the tables**, so favourites, playlists
|
||||
and resume state survive an uninstall and come back on re-install.
|
||||
|
||||
## Permissions
|
||||
|
||||
One permission, `music`, grantable per role at read or write like any other.
|
||||
|
||||
- **Read** lets an account browse the library, stream, and write its own favourites, playlists and
|
||||
now-playing state. Those are writes, but only to the caller's own rows, which is what makes them safe
|
||||
at read level.
|
||||
- **Write** adds the rest of the surface — in practice, triggering a reindex.
|
||||
|
||||
The library index itself is one shared thing for the household. Favourites, playlists and now-playing
|
||||
are scoped to the caller by the user id the platform proxy injects.
|
||||
|
||||
## Configuration
|
||||
|
||||
There is nothing to configure, and no environment variables. What the plugin reads off disk instead:
|
||||
|
||||
| On disk | What it means |
|
||||
| -------------------------------------------------- | ----------------------------------------------------------------- |
|
||||
| `~/Music/<library>/<artist>/<album>/` | The tree. Top-level folders under `Music` are libraries. |
|
||||
| `folder.jpg`, `cover.jpg`, `folder.png`, `cover.png` | Album cover, first match wins. Served compressed to ≤600px. |
|
||||
| `[1980] Back in Black` | Album folder naming — the year is parsed out for display. |
|
||||
| `_discography.md` in an artist folder | Groups that artist's albums into Studio, Live, Compilation, EP… |
|
||||
| `<track>.lrc` / `<track>.txt` beside a track | External lyrics, preferred over embedded tags. |
|
||||
|
||||
Audio: mp3, flac, m4a, aac, ogg, opus, wav, wma. Video: mp4, m4v, mkv, mov, webm, avi.
|
||||
|
||||
## How it works
|
||||
|
||||
The `officer-music` sidecar is a separate process under PM2 that listens on a loopback port and owns the
|
||||
whole contract. `/api/music/*` in the platform is a proxy: it authenticates, then forwards. It holds no
|
||||
music knowledge and no routes of its own.
|
||||
|
||||
The sidecar walks `~/Music` and writes a cache tree under the install's `data/music/cache/`, one folder
|
||||
per album, holding `meta.json`, a compressed `cover.jpg`, video posters and extracted lyrics. Each album
|
||||
carries a version stamp hashed from its files' names, sizes and mtimes plus its cover, so an incremental
|
||||
rebuild skips everything that has not changed, and a client diffs the manifest to decide what to fetch.
|
||||
|
||||
Rebuilds are triggered, never watched — a recursive `fs.watch` on this library needed one inotify watch
|
||||
per file and could not establish. The refresh button in the browser runs an incremental build; a full
|
||||
from-scratch rebuild runs nightly at 3am into a fresh slot and swaps it in atomically only on success.
|
||||
|
||||
Four tables in the platform's Postgres, all keyed on the user and cascading from it: `music_favorites`,
|
||||
`music_playlists`, `music_playlist_items`, `music_now_playing`.
|
||||
|
||||
```
|
||||
manifest.ts identity, one permission, host dependencies
|
||||
api/router.ts the proxy onto the sidecar
|
||||
sidecar/ the /api/music contract: indexing, streaming, per-user state
|
||||
db/ the four tables and their queries
|
||||
web/ the two panels, the player engine and the lyrics pane
|
||||
widgets/ a dashboard widget — parked, see below
|
||||
cliamp/ a second, server-side playback path — parked, see below
|
||||
scripts/ a CLI that drives a reindex and prints progress
|
||||
```
|
||||
|
||||
`PLUGIN.md` records how this was extracted from the platform and what it cost. It is history, not
|
||||
documentation; read `MUSIC_API.md` for the HTTP contract.
|
||||
|
||||
## Known limitations
|
||||
|
||||
Stated plainly rather than omitted.
|
||||
|
||||
- **Leaving `/music` stops playback.** The audio engine is mounted inside the library panel, so
|
||||
navigating away unmounts it. Making audio outlive the route needs either a shell slot a plugin can
|
||||
contribute to or the engine hoisted to module scope; neither exists yet. Returning to `/music`
|
||||
reloads the queue from where it was.
|
||||
- **The library listing goes through the platform's file browser**, not through this plugin, so it sits
|
||||
behind the separate `files` permission. `files` is confined: it means nothing for an account without
|
||||
its own Linux user. An account granted `music` but not `files` gets a working player, working
|
||||
favourites and an **empty library**, with no error on screen. The owner is unaffected. Moving the
|
||||
listing into the sidecar is the fix and is not done.
|
||||
- **Playlists have no web UI.** The API is complete and the mobile apps use it; the `/music` screen
|
||||
does not yet show or edit playlists.
|
||||
- **Videos are indexed but not played in the browser.** The index records them, `/stream` serves them
|
||||
and posters are generated, but the web panels ignore them. They are there for the mobile apps.
|
||||
- **The gapless engine decodes whole tracks to PCM.** That is what buys the sample-accurate seam, and
|
||||
it costs memory — roughly 200 MB for a ten-minute track, with at most three decoded buffers kept. A
|
||||
manual skip therefore has a visible decode delay; auto-advance does not, because it was decoded ahead.
|
||||
- **The index is not live.** Add an album and nothing appears until you press refresh or the nightly
|
||||
rebuild runs. A targeted per-folder reindex exists in the code but nothing calls it.
|
||||
- **`/queue` is listed in the permission's paths and no such route exists.** Carried across verbatim
|
||||
from the platform entry this replaced, kept so no existing grant changed meaning.
|
||||
- **`scripts/reindex-music.ts` looks for the sidecar port in the wrong place.** It reads
|
||||
`DATA_PATH/music/.server`, falling back to `./data`, but the platform stopped using a `DATA_PATH`
|
||||
environment variable and now derives the data directory as a sibling of the repo. Run it with
|
||||
`DATA_PATH` set explicitly, or trigger a rebuild from the UI instead.
|
||||
- **cliamp is parked.** A second playback path — the `cliamp` TUI running on the server with its audio
|
||||
piped to the browser — ships in `cliamp/` but is not wired up. It needs a plugin to be able to own a
|
||||
websocket, which the platform does not support.
|
||||
- **The dashboard widget is parked.** `widgets/` holds a working library-browsing widget that nothing
|
||||
registers, because plugins cannot contribute widgets yet.
|
||||
Reference in New Issue
Block a user