#lang scribble/manual @(require (for-label racket/base racket/contract rkt-web-player rkt-web-player/player-agent rkt-web-player/set-user rkt-web-player/users)) @title{RKT Web Player} @author[@author+email["Hans Dijkema" "hans@dijkewijk.nl"]] @defmodule[rkt-web-player] RKT Web Player is a central music server for a trusted network. It makes one or more configured music libraries available through a web interface and plays that music on different kinds of renderers: the server's own audio device, UPnP devices, Sonos groups, and player agents. The package also provides those player agents as GUI and headless CLI applications, so a computer without UPnP support can become a network renderer of its own. The central server and each agent communicate over HTTP; the agent downloads its assigned tracks and plays them on its local audio device. @section{What the player provides} The central server is the heart of the application. Its main features are: @itemlist[ @item{Multiple named music libraries, including local and UNC/network paths.} @item{Lazy directory browsing: startup reads only the library roots, while recursive contents and audio metadata are read when needed.} @item{Playback of the central library through the server's local audio device, discovered UPnP renderers, Sonos groups, or registered agents.} @item{Playlist tabs with adding, removing, reordering, renaming and drag-and-drop support. Tracks from different libraries can share a playlist.} @item{Gapless network playback where the renderer or playback agent supports preloading the next track, with a server-side fallback.} @item{Transport controls, seeking, volume, repeat mode and live playback status.} @item{Embedded album art and adjacent @tt{cover}, @tt{folder} and @tt{front} JPEG/PNG artwork.} @item{Per-user playlists, language preference and playback pipeline, with shared physical outputs that can be transferred between users.} @item{Dutch, English, German, French, Spanish, Italian, Swedish, Norwegian, Finnish and Icelandic interfaces.} @item{Optional Argon2id browser authentication and a separate default-deny allowlist for playback agents.} ] The browser is a thin remote control, not a second player. It polls a complete JSON state snapshot once per second and sends commands to the central server; playlists, preferences and playback state are kept there. The server exposes the browser application under the root URL and its JSON API under @tt{/api}. The API includes state, discovery, playback commands, preferences, artwork and the agent registration, polling and media routes. @subsection{Player agents} A player agent is an additional renderer implemented by this package. It runs on a computer connected to speakers, registers with the central server, polls for commands, downloads the selected music and plays it locally. Agents do not open an inbound network port. The package supplies two forms: @itemlist[ @item{The GUI agent, with a desktop window and system-tray integration.} @item{The headless CLI agent, suitable for a small always-on computer or a machine without a graphical desktop.} ] The server treats each registered agent as an output alongside UPnP and Sonos renderers. The server can prefetch the next track to an agent for gapless transitions. A 256-bit application ID identifies the agent; the server's @tt{[playback-agents]} allowlist decides which agents may connect. @defproc[(run-web-player [music-paths (listof (or/c path-string? (list/c string? path-string?)))] [#:allowed-agent-ids allowed-agent-ids (listof string?) null] [#:users users (listof (cons/c string? string?)) null] [#:trusted-proxies trusted-proxies (listof string?)] [#:session-seconds session-seconds exact-positive-integer? 604800] [#:listen-ip listen-ip string? "127.0.0.1"] [#:port port exact-positive-integer? 8080] [#:dlna-port dlna-port exact-positive-integer? 8734] [#:local-output? local-output? boolean? #t] [#:playlist-keystore playlist-keystore (or/c path-string? #f)] [#:log-file log-file path-string?] [#:log-retention-days log-retention-days exact-positive-integer? 7] [#:log-level log-level symbol? 'debug] [#:launch-browser? launch-browser? boolean? #t]) any/c]{ Treats every entry in @racket[music-paths] as a separate music library. A two-element list supplies an explicit display name and path. The @racket[allowed-agent-ids] list contains the 64-character application IDs that may register as polling playback agents. All agents are denied when it is empty. The function starts the web interface on @racket[listen-ip] and @racket[port]. Directory contents are browsed one level at a time. Metadata and recursive contents are only read when the user adds or plays a selected item. The @racket[dlna-port] is used to publish local audio files to a selected network renderer. Set @racket[local-output?] to @racket[#f] to omit the server's own @tt{racket-audio} output. When no other renderer is available, the player waits for discovery or playback-agent registration. Playlist tabs are atomically persisted in @racket[playlist-keystore]; @racket[#f] disables playlist persistence. Player resources are closed when the web server exits. The default is @tt{data/playlists.keystore} below the installed rkt-web-player collection. Each username owns an ordered GUID index and separate playlist values. It also owns an independent playback pipeline and a durable interface-language preference. Different outputs can play concurrently. Selecting an output that another user owns stops the previous backend and transfers that output. The web interface supports Dutch, English, German, French, Spanish, Italian, Swedish, Norwegian, Finnish and Icelandic; browser language is the initial default and a manual choice is stored per username. When @racket[users] is non-empty, every browser client must authenticate. Sessions have a sliding idle timeout; an active browser cookie is renewed once half of @racket[session-seconds] has elapsed. Playback-agent endpoints continue to use their separate application-ID allowlist. Logging is written to @racket[log-file]. Files are rotated after @racket[log-retention-days] days, and @racket[log-level] selects the @tt{simple-log} level. The default log file is @tt{data/rkt-web-player.log} below the installed collection. The @racket[playlist-keystore], @racket[log-file] and @racket[log-level] arguments are useful when embedding the player; the command-line entry point can also read these values from an INI file. The default listen address only exposes the interface to the local computer. Use a LAN address deliberately if other devices should control the player. } @section{Command line and INI configuration} The package entry point accepts one or more music directories: @verbatim{ racket main.rkt MUSIC-DIRECTORY [...] racket main.rkt --config rkt-web-player.ini --no-browser } The @tt{--config} option reads defaults from an INI file. The file is normally named @tt{rkt-web-player.ini} and is read from the current directory when the server is started with @tt{--config}. Relative paths are interpreted in the process's working directory. Command-line values for @tt{--listen-ip}, @tt{--port}, @tt{--dlna-port}, @tt{--playlist-keystore}, @tt{--log-file} and @tt{--no-browser} override the corresponding defaults. Logging retention, log level, authentication and the playback-agent allowlist are configured in the INI file. Libraries configured under @tt{[libraries]} use the INI key as their display name and the value as the root directory. The legacy @tt{[library] paths} setting accepts a semicolon-separated list and is also supported. A complete example is: @verbatim{ [server] listen-ip=127.0.0.1 port=8080 [player] dlna-port=8734 local-output=true playlist-keystore=data/playlists.keystore [logging] log-file=data/rkt-web-player.log log-retention-days=7 log-level=debug [authentication] trusted-proxies=127.0.0.0/8;::1/128 session-seconds=604800 [playback-agents] ; 64 hexadecimal characters, copied from an agent 0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef=true [users] ; Generate hashes with make-password-hash or set-user. hans=$argon2id$v=19$m=19456,t=2,p=1$... } The @tt{[server]} section controls the browser server. The default @tt{listen-ip} is @tt{127.0.0.1}, so the interface is local-only by default; @tt{0.0.0.0} or a LAN address makes it reachable from other machines. @tt{port} defaults to @tt{8080}. The @tt{[player]} section controls playback. @tt{dlna-port} defaults to @tt{8734} and must be reachable by network renderers when they play files published by the server. @tt{local-output=true} (the default) adds the server's own audio device as an output. @tt{playlist-keystore} selects the keystore for playlist tabs and language preferences; omission uses @tt{data/playlists.keystore} below the installed collection. When embedding the player through @racket[run-web-player], passing @racket[#f] disables persistence. The @tt{[logging]} section controls the rotating log file. The default file is @tt{data/rkt-web-player.log}, retention defaults to seven days, and the default level is @tt{debug}. The level is passed to @tt{simple-log}; use a level supported by that package. The @tt{[playback-agents]} section is a default-deny allowlist. Each key must be the complete 64-character hexadecimal application ID of an agent and a value other than @tt{false} permits registration. An empty section rejects all agents. This ID is a shared bearer credential, not a replacement for HTTPS. The @tt{[authentication]} section enables browser login when @tt{[users]} has at least one entry. Each user value is an Argon2id hash, not a plaintext password. @tt{session-seconds} defaults to seven days and is a sliding idle timeout. @tt{trusted-proxies} is a semicolon-separated list of IP addresses or CIDR ranges whose forwarded client address may be trusted; keep it limited to the actual reverse proxy. The application does not terminate TLS, so use HTTPS at the reverse proxy before exposing an authenticated instance beyond a trusted LAN. Create a password hash with @racket[make-password-hash], or run @racket[set-user] in the directory containing the INI file. The @racket[set-user] procedure updates @tt{rkt-web-player.ini} interactively. @subsection{Playback-agent INI file} The GUI and CLI agents use a separate private INI file named @tt{rkt-web-player-agent.ini} in the user's Racket configuration directory. It contains the following values: @verbatim{ [server] url=http://127.0.0.1:8080 [agent] name=My playback app-id=64 hexadecimal characters } The agent generates @tt{app-id} once and reuses it so the server allowlist continues to work. The GUI writes the server URL and display name; the CLI can override them with @tt{--server} and @tt{--name}. Use @tt{--config} with the CLI to select another agent INI file and therefore another identity. Agents make only outbound HTTP requests: they register and poll the server, download assigned tracks, and play them locally. They do not open an inbound port. @defmodule[rkt-web-player/users] @defproc[(make-password-hash [password string?]) string?] { Creates a salted Argon2id password hash suitable for a value in the INI @tt{[users]} section. Passwords must contain at least twelve characters. } @defproc[(password-hash-valid? [password string?] [encoded string?]) boolean?] { Checks a password against an encoded Argon2id hash. } @defmodule[rkt-web-player/set-user] @defproc[(set-user) void?] { Interactively reads a username and password and writes the corresponding Argon2id hash to the @tt{[users]} section of @filepath{rkt-web-player.ini} in the current directory. The password must contain at least twelve characters. } @defmodule[rkt-web-player/player-agent] @defproc[(run-player-agent) any/c] { Starts the graphical polling playback agent. The agent keeps a generated 256-bit application identifier in a user-specific INI file, registers with the configured RKT Web Player server, downloads assigned tracks over HTTP, and plays them with @tt{racket-audio}. Its configured display name is authoritative and is followed by the server. Importing the module does not start the GUI; the function must be called explicitly. The GUI and its @tt{racket-tray} system tray support the same ten languages based on the operating-system language, with English as fallback. Closing or minimizing the window hides it in the tray. The tray menu restores the window or shuts down the agent; no SDL3 runtime is required. } @defproc[(run-player-agent-cli [#:server-url server-url (or/c string? #f) #f] [#:name name (or/c string? #f) #f] [#:config-file config-file (or/c path-string? #f) #f]) void?] { Starts a headless playback agent using the same audio and gapless-prefetch runtime as the GUI. Missing keyword values are read from the normal agent INI file. The procedure runs until interrupted. }