188 lines
5.9 KiB
Racket
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))
|
|
]
|