1 What the player provides
The central server is the heart of the application. Its main features are:
Multiple named music libraries, including local and UNC/network paths.
Lazy directory browsing: startup reads only the library roots, while recursive contents and audio metadata are read when needed.
Playback of the central library through the server’s local audio device, discovered UPnP renderers, Sonos groups, or registered agents.
Playlist tabs with adding, removing, reordering, renaming and drag-and-drop support. Tracks from different libraries can share a playlist. The playlist toolbar shows the selected playlist’s track count and total known playing time, rounded to minutes and expressed in hours and minutes. Tracks without duration metadata contribute zero to the total.
Gapless network playback where the renderer or playback agent supports preloading the next track, with a server-side fallback.
Transport controls, seeking, volume, repeat mode and live playback status.
Embedded album art and adjacent cover, folder and front JPEG/PNG artwork.
Per-user playlists, language preference and playback pipeline, with shared physical outputs that can be transferred between users.
Dutch, English, German, French, Spanish, Italian, Swedish, Norwegian, Finnish and Icelandic interfaces.
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 player status 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 /api. The API includes state, discovery, playback commands, preferences, artwork and the agent registration, polling and media routes.
1.1 Playlist synchronization
State responses include an opaque playlistVersion for the selected playlist. The browser sends its last received version as the playlistVersion query parameter on /api/state, /api/discover and /api/command/command. When that version still matches, tracks is JSON null; otherwise the response includes the complete track array. Requests without a version continue to receive all tracks. The version and track data are returned together in one consistent snapshot.
The server caches serialized tracks, count and total duration per playlist. Changing the track list invalidates that snapshot and generates a fresh version when it is next requested. Renaming a tab or updating playback position does not invalidate the tracks. Versions are renewed after a server restart and are distinct between playlists and users. Changes from another browser are picked up by the next poll.
The browser keeps the active playlist in memory and serializes state-producing requests through PlayerStateClient, so a delayed response cannot replace newer state. Unchanged playlist rows are reused; changing the interface language still rebuilds their translated labels.
1.2 Saved playlists in the library
The library has Folders and Playlists tabs. The playlist toolbar’s Save playlist button names and saves the current tab in the user’s library. The library row’s + button opens that playlist as its own tab, or selects its existing tab. ▶ does the same and starts playback. These actions never append tracks to or replace another tab.
A saved playlist and its open tab share one UUID and one track list. Track edits and renaming therefore update the saved playlist automatically. Closing the tab keeps it in the library. Closing the last saved tab leaves a new empty default tab. Existing tabs remain open and appear in the library only after the user explicitly saves them.
The keystore retains playlists-for-username for the ordered open tabs and adds saved-playlists-for-username for library playlists. Each UUID has one stored value, shared by both indexes. The private storage procedure load-user-playlists reads the open index by default and the saved index with #:saved? #t. save-user-playlists! accepts the saved collection through #:saved and updates both indexes atomically. Values absent from both indexes are removed. Legacy stores without a saved index need no migration.
Browser state includes savedPlaylists summaries without track arrays and a saved flag on each tab. playlist-save takes an open tab’s id and a name; playlist-open and playlist-play take a saved playlist’s id. All three commands operate within the requesting user’s collection.
1.3 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:
The GUI agent, with a desktop window and system-tray integration.
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 [playback-agents] allowlist decides which agents may connect.
The server keeps an agent’s playback session and pending commands for up to two minutes without a status update, allowing brief mobile network outages. Polling within that period refreshes the connection; after longer silence, the server removes the agent from the available outputs. Already downloaded audio can continue playing on the agent during an interruption.
Both agents retain downloaded tracks in a local disk cache with a budget of 1 GB (1,000,000,000 bytes), evicting the least recently used tracks first. Selecting a cached track reuses its local file when the server address, source path, file size and modification time still match. Changing the source file’s size or modification time causes a new download. Older servers without this metadata only allow reuse for the same media token. Files in use for playback or prefetch are protected, so those files and an in-progress download can temporarily exceed the budget. The cache lasts until the agent exits; it is not retained across restarts.
A track that is not cached is still downloaded completely before playback. The next track is fetched after the play command completes. Commands are processed in server order, so a pending prefetch can still delay a later manual track selection.
procedure
(run-web-player music-paths [ #:allowed-agent-ids allowed-agent-ids #:users users] #:trusted-proxies trusted-proxies [ #:session-seconds session-seconds #:listen-ip listen-ip #:port port #:dlna-port dlna-port #:local-output? local-output?] #:playlist-keystore playlist-keystore #:log-file log-file [ #:log-retention-days log-retention-days #:log-level log-level #:launch-browser? launch-browser?]) → any/c
music-paths :
(listof (or/c path-string? (list/c string? path-string?))) allowed-agent-ids : (listof string?) = null users : (listof (cons/c string? string?)) = null trusted-proxies : (listof string?) session-seconds : exact-positive-integer? = 604800 listen-ip : string? = "127.0.0.1" port : exact-positive-integer? = 8080 dlna-port : exact-positive-integer? = 8734 local-output? : boolean? = #t playlist-keystore : (or/c path-string? #f) log-file : path-string? log-retention-days : exact-positive-integer? = 7 log-level : symbol? = 'debug launch-browser? : boolean? = #t
The dlna-port is used to publish local audio files to a selected network renderer. Set local-output? to #f to omit the server’s own racket-audio output. When no other renderer is available, the player waits for discovery or playback-agent registration. Playlist tabs are atomically persisted in playlist-keystore; #f disables playlist persistence. Player resources are closed when the web server exits. The default is 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 users is non-empty, every browser client must authenticate. Sessions have a sliding idle timeout; an active browser cookie is renewed once half of session-seconds has elapsed. Playback-agent endpoints continue to use their separate application-ID allowlist.
Logging is written to log-file. Files are rotated after log-retention-days days, and log-level selects the simple-log level. The default log file is data/rkt-web-player.log below the installed collection. The playlist-keystore, log-file and 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.