blastoise/docs/api-reference.md

5.4 KiB

Blastoise API Reference

Blastoise is a synchronized music server. The server owns channel time and queues; clients play audio locally.

Reference HTTP: http://mhsgroove.peterino.com:3001
Reference WS:   ws://mhsgroove.peterino.com:3001
Local HTTP:     http://localhost:3001
Local WS:       ws://localhost:3001

Auth is an HttpOnly cookie named blastoise_session. Same-origin browser apps can use normal fetch. Separate-origin browser apps need a same-origin proxy or CORS with credentials. Native apps must store Set-Cookie and send it as Cookie on HTTP and WebSocket requests.

Full details: api-reference-full.md

Golden Rule

Use track.id for every machine operation:

GET /api/tracks/:trackId

track.id is a content hash like sha256:.... filename and title are only for display. Queue entries, playlists, cache keys, direct links, and audio URLs should all use track.id.

Core Shapes

type PlaybackMode = "once" | "repeat-all" | "repeat-one" | "shuffle";

type Track = {
  id: string; filename: string; title: string | null;
  artist?: string | null; album?: string | null; duration: number;
  replayGainDb?: number | null; replayPeak?: number | null; available?: boolean;
};

type ChannelInfo = {
  id: string; name: string; description: string; trackCount: number;
  listenerCount: number; listeners: string[]; isDefault: boolean;
  createdBy: number | null;
};

type ChannelState = {
  track: Track | null; currentTimestamp: number; channelName: string;
  channelId: string; description: string; paused: boolean; currentIndex: number;
  listenerCount: number; isDefault: boolean; playbackMode: PlaybackMode;
  queue?: Track[];
};

type Playlist = {
  id: string; name: string; description: string; ownerId: number;
  ownerName?: string; isPublic: boolean; shareToken: string | null;
  trackIds: string[]; createdAt: number; updatedAt: number;
};

ChannelState.queue is optional. It appears on WebSocket connect, queue changes, and periodic refreshes. Keep the last known queue when omitted.

Startup

GET /api/status
GET /api/auth/me
GET /api/library
GET /api/channels
WS  /api/channels/:channelId/ws

Choose a channel: saved channel, else isDefault, else first channel.

Endpoints

Area Endpoints
Status GET /api/status
Auth POST /api/auth/signup, POST /api/auth/login, POST /api/auth/logout, GET /api/auth/me, POST /api/auth/kick-others
Channels GET /api/channels, POST /api/channels, GET/PATCH/DELETE /api/channels/:id
Playback control POST /api/channels/:id/jump, POST /api/channels/:id/seek, POST /api/channels/:id/mode
Queue PATCH /api/channels/:id/queue
Library/audio GET /api/library, GET /api/tracks/:trackId, POST /api/upload
Playlists GET/POST /api/playlists, GET/PATCH/DELETE /api/playlists/:id, PATCH /api/playlists/:id/tracks
Sharing POST/DELETE /api/playlists/:id/share, GET/POST /api/playlists/shared/:token
URL import POST /api/fetch, POST /api/fetch/confirm, GET /api/fetch, DELETE /api/fetch/:itemId, DELETE /api/fetch

Common bodies:

{ "username": "test", "password": "testuser" }
{ "name": "Channel or playlist name", "description": "optional" }
{ "mode": "repeat-all" }
{ "index": 3 }
{ "timestamp": 45.5 }

Queue and playlist track mutation:

{ "set": ["sha256:a", "sha256:b"] }
{ "add": ["sha256:c"], "insertAt": 2 }
{ "remove": [3, 4] }
{ "move": [5, 6], "to": 1 }

Remove/move use positions, not track IDs. Duplicate tracks are allowed.

Audio supports range requests:

Range: bytes=0-999999

WebSocket

Connect to:

ws://mhsgroove.peterino.com:3001/api/channels/:channelId/ws

Client messages:

{ "action": "switch", "channelId": "abc123" }
{ "action": "pause" }
{ "action": "unpause" }
{ "action": "seek", "timestamp": 45.5 }
{ "action": "jump", "index": 3 }

Server messages:

{ "type": "channel_list", "channels": [] }
{ "type": "switched", "channelId": "abc123" }
{ "type": "kick", "reason": "Kicked by another session" }
{ "type": "toast", "message": "Added: Song", "toastType": "info" }
{ "type": "scan_progress", "scanning": true, "processed": 1, "total": 20 }
{ "type": "fetch_progress", "id": "job", "status": "downloading", "progress": 50 }

Any message without type is a ChannelState.

Guests can listen and switch channels, but cannot control playback or mutate queues. Unauthorized WebSocket control messages are ignored.

Sync Algorithm

On every ChannelState:

  1. Store the state and performance.now().
  2. If state.queue exists, replace the local queue cache.
  3. If state.track is null, pause and clear the player.
  4. If state.track.id changed, set audio.src to /api/tracks/:trackId and seek to state.currentTimestamp.
  5. If same track and drift is >= 2s, seek to state.currentTimestamp.
  6. If state.paused, pause. Otherwise call audio.play().
  7. Between WebSocket updates, estimate time as state.currentTimestamp + elapsedSeconds, unless paused.

The server is the source of truth.

Gotchas

  • Some errors are JSON { "error": "..." }; some are plain text. Handle both.
  • GET /api/channels/:id does not include the queue. WebSocket connect does.
  • POST /api/playlists/shared/:token copies a playlist; there is no /copy.
  • Cache by track.id, never by filename.
  • The server does not decode audio. Clients are synchronized local players.