2026-07-31 15:59:44 +02:00
2026-07-15 18:00:11 +02:00
ja
2026-07-31 10:23:23 +02:00
2026-07-15 18:00:11 +02:00
ja
2026-07-31 10:23:23 +02:00
2026-07-15 18:00:11 +02:00
2026-07-15 18:00:11 +02:00
ja
2026-07-31 10:23:23 +02:00
2026-07-31 15:59:44 +02:00
2026-07-15 09:57:11 +02:00
2026-07-30 22:32:12 +02:00
ja
2026-07-31 10:23:23 +02:00
2026-07-29 16:22:27 +02:00
2026-07-15 18:00:11 +02:00
2026-07-15 18:00:11 +02:00
2026-07-30 22:32:12 +02:00
2026-07-15 18:00:11 +02:00
2026-07-15 18:00:11 +02:00

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

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

#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:

(upnp-device-kinds)

Without a kind, all described UPnP devices are returned:

(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

(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

(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:

(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:

(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.

S
Description
UPnP/DLNA implementation for racket
Readme Apache-2.0 145 KiB
Languages
Racket 100%