play local files to a DLNA Renderer
This commit is contained in:
@@ -0,0 +1,187 @@
|
||||
#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))
|
||||
]
|
||||
Reference in New Issue
Block a user