How the Folia Stage API Controls Player Playback: A Complete Technical Guide
The Folia Stage API is a local HTTP and WebSocket server embedded in the Folia desktop client that exposes player controls via bearer-token authentication, allowing external scripts to query playback status, manage queues, and send control commands through REST endpoints and real-time event streams.
The Folia Stage API provides a programmable interface for controlling the Folia music player from external applications. Built into the desktop client as a local-only server, it enables automation workflows, remote control scripts, and third-party integrations by exposing playback state and control mechanisms through a secure HTTP interface running exclusively on 127.0.0.1.
How the Stage API Server Starts
When stage mode is enabled via the store.get(stageModeEnabledSettingKey) setting, the createStageApi function initializes an HTTP server on a configurable local port (default 32107). The server binds strictly to 127.0.0.1, ensuring it is reachable only from the local machine and inaccessible over the network.
Authentication and Security
All API requests must include a valid bearer token for authentication. The token is generated on-demand using crypto.randomBytes(32).toString('base64url') and cached in the store under stageApiTokenSettingKey. Validation occurs through the matchesStageBearerToken function, which extracts the token via getStageBearerTokenFromRequest and performs a strict comparison, rejecting any requests with invalid or missing credentials.
REST Endpoints for Player Control
The API follows a stage-input → player-playback contract with several specialized endpoints defined in electron/stageApi.cjs:
- Status:
GET /stage/player/statusreturns a complete snapshot of the current track, queue, and playback state viabuildStagePlayerStatus. - Time:
GET /stage/player/timereturns timing-specific fields throughbuildStagePlayerTime. - Queue:
GET /stage/player/queue(paged) retrieves the current queue, whilePOST /stage/player/queuehandles append, insert, and remove operations viabuildStagePlayerQueueEventandbuildStagePlayerQueueCapabilities. - Search and Play:
POST /stage/player/searchqueries the library, andPOST /stage/player/playtriggers playback of a selected item using thestage-player-play-requesthandler. - Control:
POST /stage/player/controlaccepts JSON payloads with anactionfield (play,pause,resume,seek,next,prev), routed throughstagePlayerControlRequest.
All error responses are wrapped in StageApiError with HTTP status codes and returned via sendStageJson with CORS headers.
Real-Time Updates via WebSocket
Clients can establish persistent connections to /stage/player/ws for live event streaming. The handleStageWebSocketUpgrade function validates the bearer token, upgrades the HTTP connection, and registers the socket in the stagePlayerWebSockets set. Upon connection, the server immediately sends a STATUS message containing the current snapshot, followed by incremental events (TRACK_CHANGED, QUEUE_UPDATED, PLAYBACK_UPDATED) as the player state evolves.
Snapshot Management and Event Broadcasting
Every state-modifying operation triggers publishStagePlayerSnapshot in electron/stageApi.cjs. This function normalizes incoming data using utilities from src/utils/stagePlayerSnapshot.ts (such as normalizeStagePlayerSnapshot), detects changes to track, queue, or playback keys, and broadcasts appropriate WebSocket events to all connected clients. The implementation in src/utils/stageClientDemo.ts provides helper utilities like buildStageStatusUrl and buildStagePlayerControlUrl for constructing API endpoints.
Code Examples
Fetching Current Player Status
// Configure with your actual port and token from Folia Settings
const PORT = 32107;
const TOKEN = 'your-stage-token';
async function getStatus() {
const resp = await fetch(`http://127.0.0.1:${PORT}/stage/player/status`, {
headers: { Authorization: `Bearer ${TOKEN}` },
});
if (!resp.ok) throw new Error(`HTTP ${resp.status}`);
return resp.json(); // Returns snapshot from buildStagePlayerStatus
}
Implementation reference: buildStagePlayerStatus in electron/stageApi.cjs (source).
Sending Playback Controls
async function pausePlayback() {
const resp = await fetch(`http://127.0.0.1:${PORT}/stage/player/control`, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
Authorization: `Bearer ${TOKEN}`,
},
body: JSON.stringify({ action: 'pause' }),
});
if (!resp.ok) throw new Error(`HTTP ${resp.status}`);
const result = await resp.json();
console.log('Control executed:', result);
}
Processing flow: stage-player-control-request → publishStagePlayerSnapshot → PLAYBACK_UPDATED WebSocket broadcast. Source lines for control handling: electron/stageApi.cjs#L2027-L2032.
Managing the Queue
async function enqueueTrack(item) {
// item must include { id, source, title }
const resp = await fetch(`http://127.0.0.1:${PORT}/stage/player/queue`, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
Authorization: `Bearer ${TOKEN}`,
},
body: JSON.stringify({
action: 'append',
queueItem: item,
}),
});
if (!resp.ok) throw new Error(`HTTP ${resp.status}`);
return resp.json();
}
Queue implementation: buildStagePlayerQueueEvent in electron/stageApi.cjs (source).
Connecting to the WebSocket Feed
const ws = new WebSocket(`ws://127.0.0.1:${PORT}/stage/player/ws?token=${TOKEN}`);
ws.addEventListener('open', () => console.log('WebSocket connected'));
ws.addEventListener('message', (e) => {
const msg = JSON.parse(e.data);
console.log('Event:', msg.event, msg);
});
ws.addEventListener('close', () => console.log('WebSocket closed'));
WebSocket upgrade handler: handleStageWebSocketUpgrade in electron/stageApi.cjs (source).
Summary
- The Folia Stage API runs as a local HTTP server on port 32107, binding exclusively to
127.0.0.1for security. - Bearer token authentication protects all endpoints, with tokens generated via
crypto.randomBytesand validated bymatchesStageBearerToken. - REST endpoints provide comprehensive control over playback status, timing, queue management, and direct player actions via
/stage/player/control. - WebSocket connections at
/stage/player/wsdeliver real-time updates throughhandleStageWebSocketUpgrade, broadcastingTRACK_CHANGED,QUEUE_UPDATED, andPLAYBACK_UPDATEDevents. - Snapshot management via
publishStagePlayerSnapshotensures state consistency between the Folia player and external controllers.
Frequently Asked Questions
How do I enable the Stage API in Folia?
Enable stage mode in the Folia desktop client settings, which triggers createStageApi to start the HTTP server. The server initializes on the configured port (default 32107) only when the stageModeEnabledSettingKey store value returns true.
Is the Folia Stage API secure for local automation?
Yes. The API binds strictly to 127.0.0.1, making it inaccessible from external network interfaces. All requests must authenticate using a cryptographically secure bearer token generated by crypto.randomBytes(32).toString('base64url') and validated through matchesStageBearerToken.
What is the difference between polling the status endpoint and using WebSocket?
Polling GET /stage/player/status requires repeated HTTP requests to buildStagePlayerStatus, while the WebSocket connection at /stage/player/ws maintains a persistent pipe through handleStageWebSocketUpgrade that pushes incremental updates immediately when publishStagePlayerSnapshot detects state changes, reducing latency and resource usage.
Can I control playback from a Python script instead of JavaScript?
Yes. Any HTTP client capable of sending bearer-token headers and JSON payloads can interact with the Stage API. Python scripts using requests or httpx can authenticate with the token from stageApiTokenSettingKey and POST to /stage/player/control with actions like pause, resume, or seek.
Have a question about this repo?
These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →