166 lines
4.7 KiB
Markdown
166 lines
4.7 KiB
Markdown
# Racket UPnP module
|
|
|
|
This directory contains a small, layered UPnP control-point implementation.
|
|
The public interfaces deliberately hide SSDP, XML, SCPD and SOAP details unless
|
|
the generic service escape hatch is used.
|
|
|
|
## Module layout
|
|
|
|
```text
|
|
upnp/main.rkt generic public interface
|
|
upnp/media-renderer.rkt high-level renderer operations
|
|
upnp/media-server.rkt high-level MediaServer browser
|
|
upnp/services/av-transport.rkt playback transport
|
|
upnp/services/rendering-control.rkt volume and mute
|
|
upnp/services/connection-manager.rkt protocol and connection information
|
|
upnp/services/content-directory.rkt media-server browse and search
|
|
upnp/ssdp.rkt low-level SSDP discovery
|
|
upnp/device.rkt device-description parsing
|
|
upnp/service.rkt SCPD inspection and SOAP calls
|
|
```
|
|
|
|
The collection can be required as `upnp` when the package root is on Racket's
|
|
collection path.
|
|
|
|
## Generic device discovery
|
|
|
|
```racket
|
|
#lang racket/base
|
|
|
|
(require upnp)
|
|
|
|
(define devices
|
|
(query-upnp-devices '(media-renderer scanner)
|
|
#:interface "10.7.3.118"
|
|
#:dns? #t))
|
|
|
|
(for ([device (in-list devices)])
|
|
(printf "~a: ~a\n"
|
|
(upnp-device-kind device)
|
|
(upnp-device-name device)))
|
|
```
|
|
|
|
Known friendly device kinds are returned by:
|
|
|
|
```racket
|
|
(upnp-device-kinds)
|
|
```
|
|
|
|
Without a kind, all described UPnP devices are returned:
|
|
|
|
```racket
|
|
(query-upnp-devices)
|
|
```
|
|
|
|
Unknown vendor-specific device types are retained and classified as
|
|
`'unknown`; their original URN remains available through `upnp-device-type`.
|
|
|
|
## Service inspection
|
|
|
|
```racket
|
|
(define renderer (car (query-upnp-devices 'media-renderer)))
|
|
|
|
(for ([service (in-list (upnp-device-services renderer))])
|
|
(printf "~a: ~a\n"
|
|
(upnp-service-kind service)
|
|
(upnp-service-actions service)))
|
|
```
|
|
|
|
`upnp-service-call` is the generic escape hatch for standard services that do
|
|
not yet have a typed module and for manufacturer-specific services.
|
|
|
|
## One module per known service
|
|
|
|
```racket
|
|
(require upnp/services/av-transport
|
|
upnp/services/rendering-control)
|
|
|
|
(define transport (device-av-transport renderer))
|
|
(define rendering (device-rendering-control renderer))
|
|
|
|
(av-transport-set-uri!
|
|
transport
|
|
"http://10.7.3.118:8080/media/test.flac")
|
|
(av-transport-play! transport)
|
|
(rendering-control-set-volume! rendering 30)
|
|
```
|
|
|
|
The currently implemented typed service modules are:
|
|
|
|
- `upnp/services/av-transport`
|
|
- `upnp/services/rendering-control`
|
|
- `upnp/services/connection-manager`
|
|
- `upnp/services/content-directory`
|
|
|
|
A typed service value is still the generic `upnp-service` value. No extra
|
|
wrapper objects or public constructors are needed.
|
|
|
|
|
|
## Media-server browser
|
|
|
|
The higher media-server layer parses ContentDirectory DIDL-Lite into ordinary
|
|
containers, items and resources:
|
|
|
|
```racket
|
|
(require upnp/media-server)
|
|
|
|
(define synology
|
|
(car (query-media-servers #:interface "10.7.3.118")))
|
|
|
|
(define root (media-server-root synology))
|
|
(define music
|
|
(findf (lambda (entry)
|
|
(and (media-container? entry)
|
|
(string=? (media-entry-title entry) "Muziek")))
|
|
root))
|
|
|
|
(define entries (media-container-children synology music))
|
|
|
|
(for ([entry (in-list entries)])
|
|
(printf "~a: ~a\n"
|
|
(if (media-container? entry) 'container 'item)
|
|
(media-entry-title entry)))
|
|
```
|
|
|
|
A media item can advertise several resources. Their URLs are available with
|
|
`media-item-resources` and `media-resource-uri`. Containers are followed one
|
|
level at a time; the published hierarchy can be a logical artist/album/genre
|
|
view rather than the physical filesystem.
|
|
|
|
## Media-renderer convenience layer
|
|
|
|
Normal playback code need not mention AVTransport or SOAP:
|
|
|
|
```racket
|
|
(require upnp
|
|
upnp/media-renderer)
|
|
|
|
(define denon
|
|
(car (query-media-renderers
|
|
#:interface "10.7.3.118"
|
|
#:dns? #t)))
|
|
|
|
(media-renderer-play-uri!
|
|
denon
|
|
"http://10.7.3.118:8080/media/test.flac")
|
|
|
|
(media-renderer-seek! denon 120)
|
|
(media-renderer-set-volume! denon 30)
|
|
```
|
|
|
|
`query-media-renderers` returns all UPnP MediaRenderers by default. Add
|
|
`#:dlna-only? #t` to retain only devices advertising `X_DLNADOC`.
|
|
|
|
A later HTTP media-serving module can implement `play-file!` by publishing a
|
|
local file through Racket's web-server framework and passing the resulting URL
|
|
to `media-renderer-play-uri!`.
|
|
|
|
## Documentation and tests
|
|
|
|
The Scribble manual is in `upnp/scribblings/upnp.scrbl`.
|
|
|
|
The test file `upnp/tests/upnp-test.rkt` uses a local mock HTTP/SOAP server and
|
|
covers service discovery from SCPD, SOAP calls, SOAP faults, AVTransport,
|
|
RenderingControl, ConnectionManager, ContentDirectory and DIDL-Lite media
|
|
server browsing.
|