# How the Folia Stage API Controls Player Playback: A Complete Technical Guide

> Learn how the Folia Stage API controls player playback. This technical guide details its HTTP and WebSocket server, authentication, and endpoints for managing queues and commands.

- Repository: [冬霧/folia-major](https://github.com/chthollyphile/folia-major)
- Tags: deep-dive
- Published: 2026-07-06

---

**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/status` returns a complete snapshot of the current track, queue, and playback state via `buildStagePlayerStatus`.
- **Time**: `GET /stage/player/time` returns timing-specific fields through `buildStagePlayerTime`.
- **Queue**: `GET /stage/player/queue` (paged) retrieves the current queue, while `POST /stage/player/queue` handles append, insert, and remove operations via `buildStagePlayerQueueEvent` and `buildStagePlayerQueueCapabilities`.
- **Search and Play**: `POST /stage/player/search` queries the library, and `POST /stage/player/play` triggers playback of a selected item using the `stage-player-play-request` handler.
- **Control**: `POST /stage/player/control` accepts JSON payloads with an `action` field (`play`, `pause`, `resume`, `seek`, `next`, `prev`), routed through `stagePlayerControlRequest`.

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`](https://github.com/chthollyphile/folia-major/blob/main/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`](https://github.com/chthollyphile/folia-major/blob/main/src/utils/stageClientDemo.ts) provides helper utilities like `buildStageStatusUrl` and `buildStagePlayerControlUrl` for constructing API endpoints.

## Code Examples

### Fetching Current Player Status

```javascript
// 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](https://github.com/chthollyphile/folia-major/blob/main/electron/stageApi.cjs#L4649-L4662)).

### Sending Playback Controls

```javascript
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](https://github.com/chthollyphile/folia-major/blob/main/electron/stageApi.cjs#L2027-L2032).

### Managing the Queue

```javascript
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](https://github.com/chthollyphile/folia-major/blob/main/electron/stageApi.cjs#L4750-L4765)).

### Connecting to the WebSocket Feed

```javascript
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](https://github.com/chthollyphile/folia-major/blob/main/electron/stageApi.cjs#L645-L674)).

## Summary

- The **Folia Stage API** runs as a local HTTP server on port 32107, binding exclusively to `127.0.0.1` for security.
- **Bearer token authentication** protects all endpoints, with tokens generated via `crypto.randomBytes` and validated by `matchesStageBearerToken`.
- **REST endpoints** provide comprehensive control over playback status, timing, queue management, and direct player actions via `/stage/player/control`.
- **WebSocket connections** at `/stage/player/ws` deliver real-time updates through `handleStageWebSocketUpgrade`, broadcasting `TRACK_CHANGED`, `QUEUE_UPDATED`, and `PLAYBACK_UPDATED` events.
- **Snapshot management** via `publishStagePlayerSnapshot` ensures 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`.