You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Copy file name to clipboardExpand all lines: ARCHITECTURE.md
+8-4Lines changed: 8 additions & 4 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -4,16 +4,20 @@ Spritz implements DLNA/UPnP AV directly instead of wrapping an existing library.
4
4
5
5
## Discovery (SSDP)
6
6
7
-
Spritz sends `ssdp:alive` announcements to `239.255.255.250:1900` on startup, responds to `M-SEARCH` requests (honoring the client's `MX` delay per UPnP 1.0 §1.2.3), and sends `ssdp:byebye` on exit. Announcements repeat every 3 minutes, and each NT is sent three times with small gaps to survive datagram loss on WiFi.
7
+
Spritz sends `ssdp:alive` announcements to `239.255.255.250:1900`(IPv4) and `[FF02::C]:1900` (IPv6) on startup, responds to `M-SEARCH` requests (honoring the client's `MX` delay per UPnP 1.0 §1.2.3), and sends `ssdp:byebye` on exit. Announcements repeat every 3 minutes, and each NT is sent three times with small gaps to survive datagram loss on WiFi. `LOCATION` URLs use `[ipv6]:port` when answering an IPv6 search.
8
8
9
9
## Device description
10
10
11
-
`GET /upnp/description.xml` returns a `MediaServer:1` description advertising the ContentDirectoryand ConnectionManager services. The `<dlna:X_DLNADOC>DMS-1.50</dlna:X_DLNADOC>` tag marks it as a DLNA DMS, which strict clients (tvOS Infuse, SenPlayer) require.
11
+
`GET /upnp/description.xml` returns a `MediaServer:1` description advertising ContentDirectory, ConnectionManager, and Microsoft `X_MS_MediaReceiverRegistrar` (Xbox). The `<dlna:X_DLNADOC>DMS-1.50</dlna:X_DLNADOC>` tag marks it as a DLNA DMS, which strict clients (tvOS Infuse, SenPlayer) require.
12
12
13
13
## Browse (SOAP)
14
14
15
-
`POST /upnp/control/contentdirectory` handles `Browse`, `GetSystemUpdateID`, `GetSearchCapabilities`, and `GetSortCapabilities`. The root has three children: `V` (Videos, flat), `A` (Music, flat), and `F` (By folder, recursive). Empty containers are hidden. `<res>` tags include `size=` and DLNA.ORG flags (`OP=01` byte-seek plus standard streaming flags); file responses set `transferMode.dlna.org: Streaming` and `contentFeatures.dlna.org` so Infuse will play them.
15
+
`POST /upnp/control/contentdirectory` handles `Browse`, `Search`, `GetSystemUpdateID`, `GetSearchCapabilities`, and `GetSortCapabilities`. The root has three children: `V` (Videos, flat), `A` (Music, flat), and `F` (By folder, recursive). Empty containers are hidden. `<res>` tags include `size=`, `duration=` when the container header can be parsed, `DLNA.ORG_PN` for common profiles, and DLNA.ORG flags (`OP=01` byte-seek plus standard streaming flags). Matching sidecar subtitles (`.srt` / `.vtt` / `.ass`) are extra `<res>` URLs. Sidecar covers (`cover.jpg` / same-stem `.jpg`) appear as `<upnp:albumArtURI>` pointing at `/art/{index}`. File responses set `transferMode.dlna.org: Streaming` and `contentFeatures.dlna.org` so Infuse will play them.
16
+
17
+
A `SUBSCRIBE` to an event URL is answered with a SID and an immediate HTTP `NOTIFY` carrying the current state variables (`SystemUpdateID` stays `1` because the library is scanned once at start).
18
+
19
+
`GET /upnp/icon.png` is a 48×48 PNG listed in `iconList` on the device description.
16
20
17
21
## File serving
18
22
19
-
Each source directory is mounted at `/m/{index}/` and served over HTTP with range support via `tower-http`'s `ServeDir`.
23
+
Each source directory is mounted at `/m/{index}/` and served over HTTP with range support via `tower-http`'s `ServeFile`. Requests that leave the tree, follow a symlink, or use an unknown extension return 404. Sidecar subtitles sharing a stem with an indexed file are reachable so clients can fetch the extra `<res>` URLs. Album art is served at `/art/{index}` from `cover.jpg` / `folder.jpg` / a same-stem image next to the file.
| Samsung (Tizen) | Works | Requires `<dc:date>` on each DIDL item — included |
150
-
| LG (webOS) | Works |Shows an "unknown device" icon (no icon endpoint yet)|
165
+
| LG (webOS) | Works |Device icon served at `/upnp/icon.png`|
151
166
| Sony / Bravia | Works | Strict about `Content-Type: text/xml; charset="utf-8"` — handled |
152
167
| Apple TV — Infuse (tvOS, iOS, iPadOS) | Works | Requires the full DIDL treatment for tvOS playback |
153
168
| Apple TV — VLC (tvOS, iOS, iPadOS) | Works | tvOS VLC sometimes misses SSDP; add the M3U URL manually |
169
+
| Xbox | Works | Advertises Microsoft `MediaReceiverRegistrar`|
154
170
155
171
## Troubleshooting
156
172
157
173
DLNA is fiddly by nature, especially in combination with certain devices and operating systems (looking at you, Apple TV).
158
174
159
175
If your client can't find Spritz, check your firewall rules first (on both the server and client side, but typically the server side): SSDP needs UDP 1900 open, and HTTP needs your serving port (8080 by default).
160
176
177
+
On a VPN, Docker, or multi-homed machine, discovery may succeed while playback URLs point at the wrong address. Infuse and VLC using the M3U URL (`/spritz`) pick up the `Host` header; SSDP `LOCATION` tries to reply with the interface on the same subnet as the client.
178
+
179
+
IPv6-only LANs: SSDP also joins `[FF02::C]:1900`. When `--bind` is the default unspecified address, HTTP tries a dual-stack socket so IPv6 clients can fetch `LOCATION`. Link-local (`fe80::`) addresses are not advertised.
180
+
161
181
On Apple TV, Infuse tends to work better than VLC. If you're using VLC and can't find the share, you can bypass discovery entirely by pasting the M3U URL into VLC: `Media → Open Network Stream → http://192.168.X.X:8080/spritz`. If that plays, the server is fine and the issue is discovery.
162
182
163
183
Restart VLC once Spritz is already running — VLC only scans at startup and on NOTIFY packets.
Spritz implements DLNA/UPnP AV directly instead of wrapping an existing library. At a glance:
188
208
189
-
-**Discovery (SSDP).** Sends `ssdp:alive` on startup, responds to `M-SEARCH`, and sends `ssdp:byebye` on exit.
190
-
-**Device description.**`GET /upnp/description.xml` returns a `MediaServer:1` description advertising ContentDirectoryand ConnectionManager.
191
-
-**Browse (SOAP).**`POST /upnp/control/contentdirectory` handles `Browse` and related actions, exposing three root containers: Videos (flat), Music (flat), and By folder (recursive).
192
-
-**File serving.** Each source directory is mounted at `/m/{index}/` and served over HTTP with range support.
209
+
-**Discovery (SSDP).** Sends `ssdp:alive` on IPv4 and IPv6 on startup, responds to `M-SEARCH`, and sends `ssdp:byebye` on exit.
210
+
-**Device description.**`GET /upnp/description.xml` returns a `MediaServer:1` description advertising ContentDirectory, ConnectionManager, and Xbox MediaReceiverRegistrar.
211
+
-**Browse / Search (SOAP).**`POST /upnp/control/contentdirectory` handles `Browse`, `Search`, and related actions, exposing three root containers: Videos (flat), Music (flat), and By folder (recursive).
212
+
-**File serving.** Each source directory is mounted at `/m/{index}/` and served over HTTP with range support. Only media, sidecar-subtitle, and album-art extensions are reachable; directory listings, other files, and symlinks are not.
193
213
194
214
See [ARCHITECTURE.md](ARCHITECTURE.md) for the full protocol walkthrough.
0 commit comments