move the whole local-audio pipeline into the music sidecar
cliamp playback was implemented entirely in officer: it located the cliamp binary, validated the requested path against the owner's home, faked a PTY with `script`, injected PULSE_SINK and an ALSA config shipped inside the API tree, spawned parec to capture the sink, and set up the pulseaudio daemon and the virtual_out null sink at every boot — about 356 lines of audio-pipeline knowledge in a process that is meant to be a proxy, and none of it owned by the sidecar whose whole job is music. all of it now lives in sidecar/music: cliamp-ws.ts serves both sockets (/cliamp/ws for the player, /cliamp/audio/ws for the PCM capture) on the loopback server it already runs, pulse-audio.ts does the daemon + sink setup at sidecar startup instead of at officer's, and the asoundrc moved next to the code that passes it. officer keeps the part that is actually its job — authenticating the browser — and relays frames both ways without reading them (api/cliamp/relay.ts, same dumb-pipe shape as the vault notifications relay). the browser's frame contract is unchanged, so the frontend is not touched. two things fixed on the way: the traversal check now requires a separator after the home path, so a sibling directory whose name merely starts with it can no longer pass; and the music proxy no longer special-cases /reindex and /reindex/stream by name to extend the idle timeout — it extends the whole prefix, because a proxy should not know which of the sidecar's routes are slow. the music-specific `files` query param is out of the shared WS envelope too: upgradeWs now carries the raw query string, which any relayed provider can use. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -3,6 +3,8 @@ import { join, basename } from 'node:path';
|
||||
import type { SidecarCommand, SidecarEvent } from '../protocol';
|
||||
import { createSidecarConnector } from '../connect';
|
||||
import { streamAudioFile } from './stream-audio';
|
||||
import { cliampUpgradeData, musicWebsocket } from './cliamp-ws';
|
||||
import { ensurePulseAudio } from './pulse-audio';
|
||||
import { startNightlyReindex, stopNightlyReindex } from './nightly-reindex';
|
||||
import { startMusicWatcher, stopMusicWatcher } from './watcher';
|
||||
import {
|
||||
@@ -137,6 +139,10 @@ startNightlyReindex();
|
||||
// Recursive watcher on ~/Music → localized reindex on any change.
|
||||
startMusicWatcher();
|
||||
|
||||
// PulseAudio daemon + the `virtual_out` null sink both cliamp halves depend on. Officer used to do this at
|
||||
// its own boot, which meant every restart of a process with no audio responsibilities re-checked the sink.
|
||||
ensurePulseAudio();
|
||||
|
||||
const server = Bun.serve({
|
||||
port,
|
||||
hostname: '127.0.0.1',
|
||||
@@ -145,6 +151,15 @@ const server = Bun.serve({
|
||||
idleTimeout: 255,
|
||||
async fetch(req, server) {
|
||||
const url = new URL(req.url);
|
||||
|
||||
// The two cliamp sockets. Officer has already authenticated the browser and is relaying frames; the
|
||||
// player and the capture themselves live here (cliamp-ws.ts).
|
||||
const wsData = cliampUpgradeData(url.pathname, url.searchParams);
|
||||
if (wsData) {
|
||||
if (server.upgrade(req, { data: wsData })) return undefined as unknown as Response;
|
||||
return new Response('Expected a WebSocket upgrade', { status: 400 });
|
||||
}
|
||||
|
||||
// A from-scratch reindex can take many minutes with no bytes flowing on the triggering request.
|
||||
// Give the build endpoints a 30-min idle timeout so they aren't dropped (/manifest is a pure read now).
|
||||
if (url.pathname === '/reindex' || url.pathname === '/reindex/stream') {
|
||||
@@ -419,6 +434,7 @@ const server = Bun.serve({
|
||||
|
||||
return new Response('Not found', { status: 404 });
|
||||
},
|
||||
websocket: musicWebsocket,
|
||||
});
|
||||
|
||||
console.log(`[music] audio server listening on http://127.0.0.1:${port}`);
|
||||
|
||||
Reference in New Issue
Block a user