Initial import

This commit is contained in:
2026-07-15 18:00:11 +02:00
parent 02d961e53d
commit c6954f5109
29 changed files with 3062 additions and 2 deletions
+164 -2
View File
@@ -1,3 +1,165 @@
# racket-dlna
# Racket UPnP module
DLNA implementation for racket
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.