Files
rkt-web-player/README.md
T
2026-09-08 15:20:27 +02:00

261 lines
11 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# rkt-web-player
Een audioplayer met webinterface, gebouwd op de bestaande Racket-pakketten:
- `racket-audio` voor lokale weergave en metadata;
- `racket-upnp` en `racket-sonos` voor ontdekking van netwerkspelers;
- `racket-audio-dlna` voor het publiceren en afspelen van lokale bestanden;
- `racket-mimetypes`, `simple-ini` en `simple-log` voor ondersteunende taken.
## Starten
De gekoppelde ontwikkelpackages kunnen direct worden gebruikt:
```console
racket main.rkt D:\Muziek
```
Standaard opent de speler `http://127.0.0.1:8080/`. Iedere opgegeven map wordt
een afzonderlijke muziekbibliotheek. Bij het starten wordt alleen de inhoud van
de eerste rootmap gelezen; er vindt geen recursieve bibliotheekscan plaats.
```console
racket main.rkt --port 8090 --no-browser D:\Muziek D:\Podcasts
```
Met een klein INI-bestand zijn vaste defaults mogelijk:
```ini
[server]
listen-ip=127.0.0.1
port=8080
[player]
dlna-port=8734
local-output=true
[libraries]
muziek=D:\Muziek
podcasts=D:\Podcasts
[playback-agents]
7b4776ef27104e8eb9f7ea2c622ce76ca23de4260b0f94e6880d321017b32a0e4=true
[authentication]
trusted-proxies=127.0.0.0/8;::1/128
session-seconds=604800
[users]
hans=$argon2id$v=19$m=19456,t=2,p=1$...
```
Start dat bestand met `racket main.rkt --config rkt-web-player.ini`. Iedere key
onder `[libraries]` is de zichtbare bibliotheeknaam; de waarde is de lokale of
UNC-rootmap. Het oudere `[library] paths=D:\Muziek;D:\Podcasts` blijft eveneens
ondersteund. Playlisttabs worden standaard opgeslagen in de keystore
`data/playlists.keystore` binnen de geïnstalleerde map van rkt-web-player. Met
`playlist-keystore=...` onder `[player]` kan desgewenst een ander pad worden
gebruikt. Met `local-output=false` wordt de audio-uitvoer van de server zelf
niet als afspeelpunt aangeboden. Als nog geen netwerkspeler of playback agent
beschikbaar is, blijft de uitvoerselectie leeg totdat er een verschijnt.
Zodra `[users]` minstens één gebruiker bevat, moeten alle browserclients
inloggen, zowel lokaal als via internet. Wachtwoorden staan uitsluitend als
Argon2id-hash in de INI. Maak zo'n hash vanuit Racket:
```racket
#lang racket/base
(require rkt-web-player/users)
(displayln (make-password-hash "een lang en uniek wachtwoord"))
```
`trusted-proxies` bepaalt uitsluitend van welke directe peers de laatste
`X-Forwarded-For`-waarde wordt geaccepteerd. Laat die lijst zo klein mogelijk;
bij Apache op dezelfde machine zijn loopbackadressen voldoende. Zonder
gebruikers is authenticatie uitgeschakeld en blijft het oude gedrag behouden.
## Werking
De interface volgt de informatiearchitectuur van `rktplayer`, maar is
responsive en geschikt voor muis, toetsenbord en touch. Links staat de lazy
mappenbrowser met trackinformatie; rechts staan playlisttabs en de huidige
playlist. Een map wordt pas recursief gelezen wanneer die met **afspelen** of
**toevoegen** wordt gekozen. Trackmetadata wordt eveneens pas op dat moment
geladen. Bij de huidige track toont de webinterface embedded album-art uit de
audio-tags. Als die ontbreekt, worden naast het audiobestand ook `cover`,
`folder` en `front` met een JPEG- of PNG-extensie geprobeerd.
De webinterface praat met een kleine JSON-API onder `/api`. Lokale weergave
wordt pas geïnitialiseerd bij het eerste afspeelcommando. De knop naast de
uitvoerkeuze start UPnP-discovery; gevonden Sonos-zones worden als logische
groepen aangeboden en niet nogmaals als losse UPnP-renderers.
Voor UPnP- en Sonos-weergave publiceert de server ook de volgende playlisttrack
en stelt deze vooraf in als `NextAVTransportURI`. Een renderer die dit
ondersteunt kan daardoor zelf zonder serverronde naar de volgende track
overgaan. De server volgt de nieuwe URI en playlistindex. Renderers zonder
betrouwbare next-ondersteuning krijgen na het natuurlijke trackeinde een
servergestuurde fallback. Een expliciet stopcommando start nooit de volgende
track.
De bibliotheek links heeft de tabs **Mappen** en **Afspeellijsten**. Met
**Afspeellijst opslaan** boven de huidige playlist geef je de tab een naam en
bewaar je hem in de bibliotheek. **** bij een bewaarde playlist opent of
selecteert zijn eigen tab; **▶** opent die tab en speelt de playlist af.
Een playlist die al open is, krijgt geen tweede tab. De inhoud van andere
tabs blijft behouden. Wijzigingen in een bewaarde playlist worden automatisch
opgeslagen, ook wanneer je de tab hernoemt. Het kruisje sluit een bewaarde tab;
de playlist blijft beschikbaar in de bibliotheek.
Playlisttabs kunnen worden toegevoegd, geselecteerd, hernoemd door dubbel te
klikken en gesloten. Tracks kunnen worden afgespeeld, verwijderd en met
drag-and-drop verplaatst. Tabnamen, tabvolgorde en alle tracklijsten worden na
iedere wijziging transactioneel opgeslagen. Alleen een gesloten tab die niet
in de bibliotheek is bewaard, verdwijnt ook uit de keystore. Voor iedere gebruiker
bevat `playlists-for-<username>` de geordende lijst met geopende playlist-GUIDs,
en `saved-playlists-for-<username>` de bewaarde playlists. Onder iedere
GUID-key staan de naam en tracks van die playlist. Bestaande tabs worden niet
automatisch aan de bibliotheek toegevoegd. Tracks uit verschillende
geconfigureerde libraries mogen in dezelfde playlist staan; ontbrekende of
buiten de libraries gelegen bestanden worden bij het laden overgeslagen.
Iedere aangemelde gebruiker heeft daarbij een eigen playlistverzameling. Als
authenticatie is uitgeschakeld, wordt de verzameling van `anonymous` gebruikt.
Iedere gebruiker heeft daarnaast een eigen afspeelpipeline met uitvoerkeuze,
transportstatus, huidige track, volume en herhaalmodus. Verschillende fysieke
afspeelpunten kunnen daardoor gelijktijdig voor verschillende gebruikers
spelen. De afspeelpunten zelf blijven gedeeld: kiest een gebruiker een reeds
bezet afspeelpunt, dan wordt de vorige pipeline op dat punt gestopt en neemt de
nieuwe gebruiker het over. Een oudere
`playlists-for-local`-verzameling blijft in de keystore staan, maar wordt niet
automatisch aan een gebruiker toegewezen.
De webinterface ondersteunt Nederlands, Engels, Duits, Frans, Spaans,
Italiaans, Zweeds, Noors, Fins en IJslands. Bij
het eerste bezoek wordt de voorkeurstaal van de browser gebruikt. Een keuze in
de taalselector wordt daarna per gebruiker als `language-for-<username>` in
`data/playlists.keystore` opgeslagen en geldt daardoor ook op andere apparaten.
Zonder authenticatie geldt dit voor de gebruiker `anonymous`.
De server luistert standaard alleen op localhost. Geef alleen bewust een
LAN-adres aan `--listen-ip`. Configureer gebruikersauthenticatie voordat de
webinterface via een publiek bereikbare reverse proxy wordt aangeboden.
Gebruikers krijgen na succesvolle aanmelding een willekeurige 256-bit
sessiecookie met `Secure`, `HttpOnly` en `SameSite=Strict`. De standaard
inactiviteitsduur is zeven dagen. Geldig gebruik verschuift de servertermijn;
halverwege de termijn wordt ook de browsercookie opnieuw voor zeven dagen
uitgegeven. Sessies worden niet over een serverherstart heen bewaard. Na vijf
mislukte pogingen vanaf hetzelfde clientadres wordt aanmelden vijf minuten
geblokkeerd. De `/api/agent/*`-routes gebruiken geen gebruikerssessie: daarvoor
blijft de afzonderlijke playback-agent-allowlist gelden.
## Windows playback agent
Een lichte playback agent kan op een Windows-laptop draaien en meldt zichzelf
via HTTP bij de centrale RKT Web Player aan. De agent opent geen inkomende
netwerkpoort. Hij pollt de server voor opdrachten en rapporteert daarbij zijn
actuele afspeelstatus.
Start vanuit de broncode:
```console
racket player-agent.rkt
```
Dezelfde GUI kan vanuit een ander Racket-programma worden gestart:
```racket
#lang racket/base
(require rkt-web-player/player-agent)
(run-player-agent)
```
De GUI bewaart de server-URL, de gekozen naam en een eenmalig gegenereerde
256-bit applicatie-ID in `rkt-web-player-agent.ini` in de gebruikersspecifieke
Racket-configuratiemap. Vul als server bijvoorbeeld `http://192.168.1.10:8080`
in. De naam wordt in dezelfde agent-GUI ingesteld. Na registratie verschijnt
de agent met die naam als uitvoer van type `AGENT`; de server volgt latere
naamswijzigingen bij registratie en polling. De server laat een agent alleen
toe wanneer zijn volledige applicatie-ID vooraf als key onder
`[playback-agents]` staat en de waarde niet `false` is. Een onbekende ID krijgt
HTTP 403 en wordt niet als uitvoerpunt aangemaakt. De agent toont in dat geval
zijn ID en meldt dat de serverbeheerder deze eerst aan de INI moet toevoegen.
De GUI en het systeemvak volgen automatisch de systeemtaal en ondersteunen
dezelfde tien talen als de webinterface.
Onder **Afspelen** toont de agent het playlistnummer, de huidige track en
bestandsnaam, afspeeltoestand, verstreken en totale tijd, bitdiepte,
samplefrequentie, kanaalaantal en decoderformaat.
Een geselecteerde track wordt volledig naar een tijdelijk bestand gedownload
voordat `racket-audio` de weergave start. Tijdens het afspelen stuurt de server
al een `prefetch`-opdracht voor de volgende playlisttrack. De agent bewaart
daardoor maximaal het huidige en het volgende bestand lokaal. Bij de overgang
opent de agent het vooraf opgehaalde bestand direct bij decoder-EOF. Daardoor
blijven polling en netwerkvertraging buiten het tijdkritische audiopad en kan
`racket-audio` de volgende track achter de nog uitspelende uitvoerbuffer zetten.
De server wordt bijgewerkt zodra het nieuwe music-id werkelijk hoorbaar is en
stuurt vervolgens de daaropvolgende prefetch. Tijdelijke bestanden worden
opgeruimd zodra ze niet meer nodig zijn en bij afsluiten van de agent.
### Systeemvak
De GUI gebruikt het Racket-pakket `racket-tray`. Minimaliseren en de
vensterknop verbergen de agent in het systeemvak. Het menu bevat
**RKT Web Player Agent openen** en **Afsluiten**; openen herstelt ook een
geminimaliseerd venster. De tray is onderdeel van de normale package-
dependencies en vereist geen SDL3-pakket of SDL3-libraries meer.
Windows en macOS gebruiken de native systeemvoorzieningen zonder aanvullende
runtime. Op Linux gebruikt `racket-tray` Ayatana AppIndicator voor GTK3. Op
Debian en Ubuntu kan die runtime zo worden geïnstalleerd:
```console
sudo apt install libayatana-appindicator3-1
```
### CLI playback agent
De headless agent gebruikt exact dezelfde polling-, download-, audio- en
gapless-prefetchkern als de GUI-agent:
```console
racket player-agent-cli.rkt
racket player-agent-cli.rkt --server https://muziek.example.nl --name "Werkkamer"
```
GUI en CLI gebruiken standaard hetzelfde gebruikersspecifieke
`rkt-web-player-agent.ini`, dus ook hetzelfde application-ID. Met `--config`
kan de CLI een afzonderlijk INI-bestand en daarmee een afzonderlijke identiteit
gebruiken. Bij het starten worden naam, server en application-ID afgedrukt;
stoppen gaat met `Ctrl+C`.
Vanuit Racket is de headless variant eveneens beschikbaar:
```racket
(require rkt-web-player/player-agent)
(run-player-agent-cli #:server-url "https://muziek.example.nl"
#:name "Werkkamer")
```
De allowlist voorkomt dat een onbekende agent zich als uitvoerpunt registreert
of opdrachten en tijdelijke media ontvangt. Het applicatie-ID is een gedeeld
toegangstoken en vervangt geen transportbeveiliging of gebruikersauthenticatie.
Gebruik de agent en server alleen op een vertrouwd LAN zolang de HTTP-server
geen TLS heeft.
## Controleren
```console
raco test private/users.rkt private/library.rkt private/player.rkt \
private/server.rkt private-player-agent/player-agent-config.rkt
node tests/player-state.test.mjs
node tests/playlist-library.test.mjs
raco setup --check-pkg-deps rkt-web-player
```