#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)) ]