Files
2026-07-30 22:32:12 +02:00

187 lines
5.2 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/didl-lite.rkt DIDL-Lite metadata generation
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 files
(start-media-file-server
"http://10.7.3.118:8080/media/"
#:listen-ip "10.7.3.118"))
(define uri
(media-file-server-publish!
files
"C:/muziek/test.flac"
"test.flac"))
(define denon
(car (query-media-renderers
#:interface "10.7.3.118"
#:dns? #t)))
(media-renderer-play-uri!
denon
uri
#:metadata
(media-file-server-didl-lite
files
uri
#:title "Test track"
#:duration 639))
(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`.
For local files, `media-file-server-publish!` supplies a byte-range-capable
HTTP URL. `media-file-server-didl-lite` generates matching DIDL-Lite metadata,
including MIME type, file size, duration, and `DLNA.ORG_OP=01`. Some renderers,
including certain Denon models, only advertise `Seek` after receiving this
metadata. Generate and pass matching metadata for both the current URI and a
preloaded next 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.