Files
racket-audio-dlna/scribblings/racket-audio-dlna.scrbl
T
2026-08-28 15:56:19 +02:00

238 lines
7.9 KiB
Racket

#lang scribble/manual
@(require
(for-label
racket/base
racket/contract
racket-audio-dlna
racket-upnp/media-file-server
racket-upnp/media-renderer))
@title{racket-audio-dlna}
@author{Hans Dijkema}
@defmodule[racket-audio-dlna]
This package plays local audio files on UPnP and DLNA media renderers. It
uses @racketmodname[racket-audio/taglib] for metadata and audio properties,
and the @tt{racket-upnp} package for HTTP publication, DIDL-Lite, and renderer
control.
@section{Creating a player}
@defproc[(make-dlna-player
[renderer media-renderer?]
[#:media-file-server file-server
(or/c #f media-file-server?) #f]
[#:listen-ip listen-ip (or/c #f string?) #f]
[#:port port exact-integer? 8080]
[#:path path string? "/racket-audio-dlna/"]
[#:poll-seconds poll-seconds (and/c rational? positive?) 1.0]
[#:volume-poll-seconds
volume-poll-seconds (and/c rational? positive?) 5.0])
dlna-player?]{
Creates a player for @racket[renderer]. When @racket[file-server] is
@racket[#f], the player starts and owns a local HTTP media server. When a
@racket[media-file-server?] is supplied, the player publishes through that
shared server in an automatically generated private path. Closing the player
removes its publications but does not stop the shared server.
The @racket[listen-ip], @racket[port], and @racket[path] arguments configure
only a media server created by the player. When @racket[listen-ip] is
@racket[#f], the address is selected from the network route to the renderer.
The player polls transport state and position every @racket[poll-seconds].
Volume and mute state are refreshed every @racket[volume-poll-seconds].
}
@defproc[(dlna-player? [value any/c]) boolean?]
@section{Files and playback}
@defproc[(dlna-player-play!
[player dlna-player?]
[file path-string?])
void?]{
Reads the file with TagLib, publishes it through the internal HTTP server,
generates matching DIDL-Lite, installs the URI, and starts playback. Missing
titles fall back to the file name. Other missing tags are omitted.
}
@defproc[(dlna-player-set-next-file!
[player dlna-player?]
[file path-string?])
void?]{
Publishes and preloads the file with @tt{SetNextAVTransportURI}. Replacing the
next file removes the previous next-file publication.
}
@defproc[(dlna-player-play-uri!
[player dlna-player?]
[uri string?]
[#:metadata metadata string? ""]
[#:protocol-info protocol-info (or/c #f string?) #f]
[#:mime-type mime-type (or/c #f string?) #f]
[#:track track (or/c #f dlna-track-info?) #f])
void?]{
Installs an existing HTTP media URI directly on the renderer and starts
playback. The URI is not published or owned by this player. Optional
DIDL-Lite metadata and track information are retained in the player state.
When @racket[protocol-info] or @racket[mime-type] is supplied, the resource
capabilities are resolved by @racketmodname[racket-upnp] immediately before
playback and matching DIDL-Lite is generated automatically. In that case
@racket[metadata] is ignored.
}
@defproc[(dlna-player-set-next-uri!
[player dlna-player?]
[uri string?]
[#:metadata metadata string? ""]
[#:protocol-info protocol-info (or/c #f string?) #f]
[#:mime-type mime-type (or/c #f string?) #f]
[#:track track (or/c #f dlna-track-info?) #f])
void?]{
Preloads an existing HTTP media URI with @tt{SetNextAVTransportURI}. Replacing
it never attempts to remove the external resource.
}
@defproc[(dlna-player-pause! [player dlna-player?]) void?]
@defproc[(dlna-player-resume! [player dlna-player?]) void?]
@defproc[(dlna-player-stop! [player dlna-player?]) void?]
@defproc[(dlna-player-seek!
[player dlna-player?]
[seconds (and/c rational? (not/c negative?))])
void?]{
Seeks using the AVTransport @tt{REL_TIME} unit.
}
@defproc[(dlna-player-seek-percentage!
[player dlna-player?]
[percentage real?])
void?]{
Converts the clamped percentage to seconds using the cached track duration and
then seeks with @racket[dlna-player-seek!].
}
@section{Volume and mute}
@defproc[(dlna-player-volume!
[player dlna-player?]
[percentage real?])
void?]
@defproc[(dlna-player-muted!
[player dlna-player?]
[muted? boolean?])
void?]
Values are limited to the renderer range where applicable. A successful
command updates the cache immediately.
@section{Cached information}
@defproc[(dlna-info? [value any/c]) boolean?]{
Returns @racket[#t] when @racket[value] is a cached player snapshot.
}
@defproc[(dlna-player-info
[player dlna-player?]
[#:refresh? refresh? boolean? #f])
dlna-info?]{
Returns the cached player snapshot. While playing, the cached position is
advanced locally between renderer polls and limited to the known duration.
When @racket[refresh?] is true, transport, position, volume, and mute state are
queried synchronously before the snapshot is returned. A failed refresh keeps
the previous values and sets @racket[dlna-info-reachable?] to @racket[#f].
}
@defproc[(dlna-info-state [info dlna-info?]) symbol?]
@defproc[(dlna-info-track [info dlna-info?])
(or/c #f dlna-track-info?)]
@defproc[(dlna-info-uri [info dlna-info?]) (or/c #f string?)]
@defproc[(dlna-info-next-track [info dlna-info?])
(or/c #f dlna-track-info?)]
@defproc[(dlna-info-next-uri [info dlna-info?]) (or/c #f string?)]
@defproc[(dlna-info-position [info dlna-info?]) (or/c #f real?)]
@defproc[(dlna-info-duration [info dlna-info?]) (or/c #f real?)]
@defproc[(dlna-info-volume [info dlna-info?])
(or/c #f exact-nonnegative-integer?)]
@defproc[(dlna-info-muted? [info dlna-info?]) (or/c #f boolean?)]
@defproc[(dlna-info-reachable? [info dlna-info?]) boolean?]
@section{Track metadata}
@defproc[(dlna-track-info? [value any/c]) boolean?]{
Returns @racket[#t] when @racket[value] contains cached track metadata.
}
@defproc[(dlna-track-info-file [track dlna-track-info?]) path?]
@defproc[(dlna-track-info-title [track dlna-track-info?]) string?]
@defproc[(dlna-track-info-artist [track dlna-track-info?])
(or/c #f string?)]
@defproc[(dlna-track-info-album [track dlna-track-info?])
(or/c #f string?)]
@defproc[(dlna-track-info-genre [track dlna-track-info?])
(or/c #f string?)]
@defproc[(dlna-track-info-year [track dlna-track-info?])
(or/c #f exact-positive-integer?)]
@defproc[(dlna-track-info-track [track dlna-track-info?])
(or/c #f exact-positive-integer?)]
@defproc[(dlna-track-info-duration [track dlna-track-info?])
(or/c #f exact-positive-integer?)]
@defproc[(dlna-track-info-sample-rate [track dlna-track-info?])
(or/c #f exact-positive-integer?)]
@defproc[(dlna-track-info-bit-rate [track dlna-track-info?])
(or/c #f exact-positive-integer?)]
@defproc[(dlna-track-info-channels [track dlna-track-info?])
(or/c #f exact-positive-integer?)]
TagLib results are cached by complete file name, size, and modification time.
Changing the file therefore invalidates its cached metadata.
@section{Closing}
@defproc[(dlna-player-close! [player dlna-player?]) void?]{
Stops playback, removes current and next publications, and terminates the
polling thread. A media server created by the player is also stopped. A shared
server supplied to @racket[make-dlna-player] remains running. Repeated calls
have no effect.
}
@section{Example}
@racketblock[
(define renderer
(get-media-renderer "Denon"))
(define player
(make-dlna-player renderer))
(dlna-player-play!
player
"/muziek/radiohead/in-rainbows.flac")
(dlna-player-set-next-file!
player
"/muziek/radiohead/bodysnatchers.flac")
(define info
(dlna-player-info player))
(printf "~a: ~a / ~a seconds, volume ~a\n"
(dlna-info-state info)
(dlna-info-position info)
(dlna-info-duration info)
(dlna-info-volume info))
]