creaturesurvive/upnpcast
Cast to smart TVs, AV receivers and speakers over DLNA/UPnP from Swift. It includes modern
Why
Most TVs sold in the last decade (Samsung, LG, Sony, Panasonic, Philips) are DLNA MediaRenderers, as are Kodi, VLC, many AV receivers and Sonos speakers. Media apps like Infuse and VLC support "Play To" for exactly this reason. Swift has had no maintained way to do it: the existing libraries are Objective-C or abandoned, and depend on outdated XML and socket stacks.
UPnPCast complements Google Cast libraries such as SwiftCast and AirPlay (AVRoutePickerView), so a media app can reach nearly every screen in the house.
Features
- SSDP discovery:
M-SEARCHwith retransmission, deduplication andNOTIFYparsing, streamed
as an AsyncThrowingStream.
- Device descriptions: full parsing including embedded devices (Sonos-style),
URLBase,
relative URL resolution, icons, and namespace-tolerant parsing for sloppy firmware.
- SOAP: invoke any action on any service. UPnP faults are thrown with their code and
description.
MediaRenderer:
- AVTransport: load, play, pause, stop, seek, next, previous, SetNextAVTransportURI, transport info, position info - RenderingControl: volume and mute - ConnectionManager: supported formats, plus supports(mimeType:) - status() fetches transport, position and volume in parallel, and statusUpdates() polls changes as an async stream
- DIDL-Lite metadata with correct double escaping, DLNA
protocolInfoflags that renderers
accept for seekable streams, artwork, duration and size. External subtitles are included using both sec:CaptionInfoEx (Samsung and others) and extra <res> entries.
UPnPTimeparsing and formatting, including fractional (F0/F1) forms andNOT_IMPLEMENTED.- Swift 6 strict concurrency; everything is
Sendable.
Installation
Add UPnPCast to your Package.swift:
dependencies: [
.package(url: "https://github.com/CreatureSurvive/UPnPCast.git", from: "1.0.0"),
],
targets: [
.target(name: "MyApp", dependencies: ["UPnPCast"]),
]Or in Xcode, choose File › Add Package Dependencies… and enter https://github.com/CreatureSurvive/UPnPCast.
Requirements
| Platform | Minimum | | --- | --- | | iOS | 16.0 | | macOS | 13.0 | | tvOS | 16.0 | | visionOS | 1.0 |
Swift 6.1 (Xcode 16.4) or later, in Swift 6 language mode. No third-party dependencies.
Entitlements (iOS, tvOS, visionOS)
SSDP uses UDP multicast, which Apple gates behind an entitlement:
- Request com.apple.developer.networking.multicast from Apple
(form) and add it to your entitlements.
- Add
NSLocalNetworkUsageDescriptionto Info.plist.
You can skip discovery by connecting to a known renderer with MediaRenderer.connect(descriptionURL:), which only needs local network permission. macOS needs no entitlement.
Usage
Discovery
// Media renderers, ready to control
let renderers = try await MediaRenderer.discover(duration: .seconds(3))
// Anything UPnP
for try await response in SSDP.search(target: SSDP.SearchTarget.all) {
let device = try await UPnPDevice.load(from: response.location)
print(device.friendlyName, device.deviceType)
}Playback and status
try await tv.load(item) // SetAVTransportURI
try await tv.play()
try await tv.pause()
try await tv.setNext(nextItem) // gapless, where supported
for try await status in tv.statusUpdates(every: .seconds(1)) {
print(status.transport.state, status.position.position ?? 0, status.position.duration ?? 0)
}Format negotiation
if await tv.supports(mimeType: "video/x-matroska") {
// direct play
} else {
// ask your server (Jellyfin, Plex, …) for an MP4/HLS transcode
}Raw SOAP
let service = device.service(ofType: UPnPServiceType.avTransport)!
let output = try await SOAPClient().invoke("GetMediaInfo", on: service, arguments: ["InstanceID": "0"])
print(output["NrTracks"], output["CurrentURI"])Tips for media servers
- Serve media over plain HTTP on the LAN. Many renderers reject HTTPS and self-signed
certificates.
- Put auth tokens in the URL query (for example Jellyfin's
api_key). Renderers can't send custom
headers.
- Support HTTP
Rangerequests. Renderers seek with byte ranges.
Testing
swift test runs parsing, formatting and escaping tests, plus end-to-end MediaRenderer tests against an in-process mock renderer that serves a real description document and SOAP responses, including faults. UPNP_LIVE_TESTS=1 swift test also discovers and describes the real UPnP devices on your network.
Changelog
See CHANGELOG.md. Releases follow Semantic Versioning.
Contributing
Issues and pull requests are welcome. Please run swift test before opening a pull request, and add tests for new behavior.
License
Available under the MIT license. See LICENSE for details.
Package Metadata
Repository: creaturesurvive/upnpcast
Default branch: main
README: README.md