Files

188 lines
5.9 KiB
Racket

#lang scribble/manual
@(require
(for-label
racket/base
racket/contract
racket-audio-dlna
racket-upnp))
@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 @racketmodname[racket-upnp] for HTTP publication, DIDL-Lite, and renderer
control.
@section{Creating a player}
@defproc[(make-dlna-player
[renderer media-renderer?]
[#: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] and starts its local HTTP media server.
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-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-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-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, stops the HTTP server,
and terminates the polling thread. 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))
]