mirror of
https://github.com/KRTirtho/spotube.git
synced 2026-11-04 10:13:59 +00:00
- Add jam-server/: a Cloudflare Worker with one Durable Object per room code. It tracks membership, host handoff and kicks/bans, keeps the queue for late joiners, routes guest commands to the host and serves the room clock. - Sync playback: devices estimate the server clock, the host sends position anchors in server time, guests follow by nudging playback rate (seeking only when far off) and stream the host's audio source when they share a plugin. - Seek, play/pause, skip and jump apply to the whole room. - Recover from network loss instead of crashing: socket failures reconnect with backoff, silent sockets are detected, and an expired room is recreated by its host. - Keep cost and battery low: anchors only on change (plus host drift and a 60 s heartbeat), kept in memory rather than storage, free runtime keep-alives once a minute, rare clock re-syncs, and no polling while paused or buffering. - Remove the MQTT broker settings and libraries. - Add sync unit tests, client/session integration tests (JAM_TEST_URL) and a network-loss test, plus a deploy workflow for the server.
73 lines
2.9 KiB
Markdown
73 lines
2.9 KiB
Markdown
# Spotube Jam server
|
|
|
|
The room server behind Group Jam: a Cloudflare Worker with one
|
|
[Durable Object](https://developers.cloudflare.com/durable-objects/) per room code.
|
|
|
|
Each room relays a few small JSON frames between the host and its guests. Nobody streams
|
|
audio through it (every device fetches its own), so a room costs a few KB per minute.
|
|
|
|
Rooms are billed per incoming message (20 count as one request) and per storage write,
|
|
so steady playback sends almost nothing:
|
|
|
|
- The host sends a playback anchor only when playback changes (play/pause, seek, skip,
|
|
speed), when its player drifts from its last anchor, and once a minute as a safety net.
|
|
Guests extrapolate anchors with the shared clock in between.
|
|
- Anchors are kept in memory, not storage. A room that hibernated wakes for a join, and
|
|
the host answers the join with a fresh anchor. Only queue changes and membership
|
|
bookkeeping are written.
|
|
- Keep-alives (`"ping"` → `"pong"`) are answered by the runtime without waking the room.
|
|
Clock pings go out on join and every 10 minutes.
|
|
|
|
A four-person room sends roughly 200 messages an hour (about 10 billed requests).
|
|
|
|
## What a room does
|
|
|
|
- **Membership**: tracks who is connected, who hosts, and who is banned. If the host
|
|
drops, it keeps hosting for 30 s (so it can reconnect, e.g. after switching networks), then the longest-connected guest
|
|
takes over.
|
|
- **Retained state**: keeps the host's latest queue (and, while awake, its playback
|
|
anchor) so late joiners start in sync. An empty room keeps them for 10 minutes, then deletes itself.
|
|
- **Routing**: guest commands (play/pause, skip, seek, suggestions) go to the host.
|
|
Kicks and bans are enforced by the server.
|
|
- **Clock**: answers pings with its own time. Every device syncs playback to this one
|
|
clock (see `JamClock` in the app).
|
|
|
|
The wire protocol is in [`src/protocol.ts`](src/protocol.ts). The app side is
|
|
`composeApp/src/commonMain/kotlin/org/spotube/spotube/core/jam/`.
|
|
|
|
## Develop
|
|
|
|
```sh
|
|
npm install
|
|
npm run dev # http://127.0.0.1:8787
|
|
npm test # smoke test against the dev server (another terminal)
|
|
npm run typecheck
|
|
```
|
|
|
|
The app's end-to-end tests run against it too:
|
|
|
|
```sh
|
|
JAM_TEST_URL=http://127.0.0.1:8787 ./gradlew :composeApp:jvmTest --tests 'org.spotube.spotube.core.jam.*'
|
|
```
|
|
|
|
## Deploy
|
|
|
|
```sh
|
|
CLOUDFLARE_API_TOKEN=... CLOUDFLARE_ACCOUNT_ID=... npx wrangler deploy
|
|
```
|
|
|
|
The token needs the "Edit Cloudflare Workers" permissions. Durable Objects on the
|
|
SQLite backend work on the free plan. `.github/workflows/jam-server-deploy.yml` deploys on
|
|
pushes to `master` that touch this directory (secrets `CLOUDFLARE_API_TOKEN` and
|
|
`CLOUDFLARE_ACCOUNT_ID`; optional variable `JAM_SERVER_URL` runs the smoke test after).
|
|
|
|
## Self-host
|
|
|
|
Deploy to your own Cloudflare account as above, then enter the Worker's URL in
|
|
Spotube → Settings → Jam → Server URL.
|
|
|
|
## Limits
|
|
|
|
- 32 participants per room, 512 KB per frame.
|
|
- 60 room connections per minute per IP (`CONNECT_LIMITER` in `wrangler.jsonc`).
|