rox

Ein Player, auf dem du bauen kannst

rox hat, wofür andere Player einen Webserver einbauen: einen lokalen JSON-RPC-Socket, Ereignisse, die gepusht werden, sobald sich etwas bewegt, einen MCP-Proxy und einen Icecast-Stream nach draußen.

Der Server, der rox nicht sein will

Der übliche Weg, wie ein Desktop-Player sich öffnet, ist ein eingebauter Webserver. Das kauft Fernsteuerung und kostet alles andere: einen Port auf deiner Maschine, eine Auth-Geschichte, eine Web-UI, die den eigenen Look des Players aufgibt, und eine Angriffsfläche, die bei einem Musikplayer nichts verloren hat. Jeder Nutzer zahlt dafür, damit die wenigen, die ihren Player skripten, es können.

rox behält die Fähigkeit und verweigert die Lieferung. Seine Maschinenschnittstelle ist ein lokaler Socket, ein Unix Domain Socket unter Linux und macOS und eine Named Pipe unter Windows, gebunden an den Datenordner, damit zwei portable Instanzen sich nie in die Quere kommen. Auth sind Dateisystemrechte: Alles, was deine Musik lesen könnte, darf schon, was der Socket erlaubt. Nichts lauscht auf einem Port, und kein HTTP-Server steckt in der Binärdatei.

Ein Socket, der ganze Player

Das Protokoll ist JSON-RPC 2.0, ein Objekt pro Zeile, eröffnet mit einem Versions-Handshake. transport.* bedient das Deck: Umschalten, Spulen, Lautstärke, die A-B-Schleife. queue.* bearbeitet die Reihenfolge über stabile Eintrags-IDs, die Zeile, die du verschoben hast, ist also noch die Zeile, die du meintest. library.* sucht, liefert die vollen Tags des laufenden Titels und reicht Coverbilder heraus. library.rescan und tasks.* starten, beobachten und stoppen das erneute Einlesen und die Analysedurchläufe, und die Antwort auf den Start trägt die Titelzahl und die Zeitschätzung, die dir das Fenster gezeigt hätte. Jede Methode liest und bedient denselben Player und dieselbe Bibliothek wie die Panels, der Socket kann also nie etwas sagen, was die UI nicht sagen würde.

Das Vorbild sind mpvs JSON-IPC und mpds Protokoll, und der Preis ist auch ihrer: Ein Konsument braucht einen Socket-Client, wo ein Server curl geboten hätte. roxctl, der Referenz-Client im Quellbaum, deckt den Shell-Fall ab, ein Aufruf pro Kommando, --json, wenn ein Skript mitliest, roxctl next und roxctl search miles davis funktionieren also, bevor du eine Zeile von irgendwas geschrieben hast.

Push, damit du nie pollst

Ein Frontend, das nicht abonnieren kann, wird pollen, also stecken Ereignisse seit Version eins im Vertrag. Ruf subscribe auf, und der Socket pusht Frames, sobald sich etwas bewegt: event.track beim Titelwechsel, event.playback, wenn sich Wiedergabezustand, Lautstärke oder Stummschaltung ändern, event.queue, wenn die Reihenfolge es tut.

Ein Konsument, der nicht hinterherkommt, wird getrennt statt abgewartet, ein hängender Leser kann sich also nie in die Wiedergabe zurückstauen. roxctl watch druckt den Strom, wie er ankommt, und das ist eine Titelanzeige für die Statusleiste, gelöst in einer Shell-Schleife.

MCP, das kein anderer Player hat

Neben der App sitzt rox-mcp, eine schlanke Binärdatei, die auf der einen Seite MCP über stdio spricht und auf der anderen den Socket. Richte Claude oder einen beliebigen MCP-Client darauf, und der Player wird zu Werkzeugen: was läuft, die Bibliothek durchsuchen, die Warteschlange lesen, den Transport bedienen, ein erneutes Einlesen oder einen Analysedurchlauf starten. „Leg was Ruhiges aus den 70ern in die Warteschlange“ hört auf, ein Feature zu sein, das rox erst bekommen müsste, und wird ein Satz, auf den dein Assistent reagieren kann.

Jedes Werkzeug leitet an eine Socket-Methode weiter, die MCP-Oberfläche ist also per Konstruktion eine Teilmenge dessen, was der Socket bedient, und kann ihm nicht davonlaufen. Und es ist doppelt Opt-in: Ein Schalter für KI-Funktionen, standardmäßig aus, gibt eine MCP-Einstellungsseite mit eigenem Schalter frei, und jeder Werkzeugaufruf prüft die Schalter in der laufenden App neu, bevor er antwortet. Aus heißt klare Absage, kein Hänger.

Die Audio-Hälfte

Steuerung ist die eine Hälfte eines Frontends; die andere ist, etwas zu hören. rox verbindet sich als Quell-Client nach draußen zu einem Icecast-Server und schiebt den verarbeiteten Stream, kodiert als MP3, an den Mount, den du nennst. Der Abgriff kommt nach der Verarbeitungskette, der Broadcast ist also genau das, was die Lautsprecher bekommen, Equalizer inklusive.

Die Richtung ist der Punkt. rox verbindet sich nach draußen, es serviert nie: der Mount, die Hörer und die Seite zum Netz hin gehören alle zu Icecast. Ein unerreichbarer Server lässt Chunks fallen, statt die lokale Wiedergabe anzurühren, und die Senke verbindet sich nach eigenem Takt neu, solange die Konfiguration da ist.

Was das aufmacht

Jedes Teil ist für sich nützlich. Ein Medientasten-Daemon oder ein Statusleisten-Widget ist der Socket und ein paar Zeilen. Ein Hardware-Controller, ein Stream Deck oder ein Drehknopf an einem Mikrocontroller, ist transport.* von allem aus, was einen Socket erreicht. Kiosk-Dienst für einen Hörraum sind die Warteschlangen-Methoden und event.track an einer Anzeige.

Setz sie zusammen, und rox ist das Backend eines persönlichen Streaming-Aufbaus: Transport und Warteschlange über den Socket, Audio eingebettet vom Icecast-Mount, und ein Frontend in was auch immer du magst, während deine Bibliothek auf der Maschine spielt, auf der sie liegt.

Bring einen Socket-Client mit

Der Socket ist an, wo immer rox läuft, rox-mcp liegt neben der App, und die MCP-Seite in den Einstellungen hat die Konfigurationszeile für deinen Client. roxctl baut sich aus dem Quellcode mit einem Cargo-Befehl.