Skip to content

feat: UPnP/DLNA renderer control — pure wire-protocol half (#41) - #90

Open
ErfanBagheri404 wants to merge 2 commits into
mainfrom
feat/upnp
Open

ErfanBagheri404 wants to merge 2 commits into
mainfrom
feat/upnp

Conversation

@ErfanBagheri404

@ErfanBagheri404 ErfanBagheri404 commented Sep 28, 2026 •

Copy link
Copy Markdown
Owner

What

The testable half of UPnP playback: everything a renderer speaks on the wire, with no native dependency.

  • SSDP — reply parsing, USN device-prefix merging (one reply per service), device-description read, absolute control-URL resolution. Devices without AVTransport are refused, so they never reach the picker.
  • DIDL-Lite — metadata documents with duration, protocolInfo, artwork, and a zero-padded queue index for the renderers that sort by it.
  • SOAP — SetAVTransportURI / SetNextAVTransportURI / Play / Stop / Set+GetVolume / GetPositionInfo, URN headers, per-action routing (volume to RenderingControl, transport to AVTransport), fault + RelTime parsing.

What this does NOT do

The discovery half — SSDP multicast sockets — needs a small native module (React Native has no multicast stdlib). That is the remaining work, and this PR is written so the native module only has to produce strings this module already parses.

Equally deliberate: renderers can only play URLs they can reach. googlevideo URLs are signed and Expiring, so the queue handed to a renderer is limited to phone-reachable sources (Subsonic/Navidrome, local files served over the LAN, direct http). The scope note in the module header says this plainly.

Tests

node tests/upnp-renderer-regression.cjs — 24/24.

Sabotage-verified: routing volume to the transport URL drops it to 23/24.

Summary by Sourcery

Implement the pure wire-protocol layer for controlling UPnP/DLNA renderers.

New Features:

  • Add pure UPnP/DLNA renderer protocol support for SSDP parsing, device description handling, DIDL-Lite metadata, and SOAP playback, volume, and position actions.

Enhancements:

  • Resolve renderer control URLs, merge device services, route transport and volume actions to their respective services, and handle service-specific versions and protocol responses.
  • Reject renderers without AVTransport and document the restriction to phone-reachable media URLs.

Tests:

  • Add regression coverage for SSDP, device descriptions, DIDL-Lite metadata, SOAP requests and responses, routing, service versions, and native-import scope.

Closes #41

…trol (#41)

- modules/upnpRenderer.ts: SSDP reply parse + USN device merge, device description read, absolute control URLs, AVTransport-gated renderer build
- DIDL-Lite metadata with duration/protocolInfo/artwork, zero-padded queue index
- SOAP: SetAVTransportURI/SetNext/Play/Stop/Set+GetVolume/GetPositionInfo, URN headers, per-action control URL routing, fault + RelTime parse
- scope note: renderers can only play URLs they can reach (Subsonic/direct http); phone-resolved signed URLs are out of scope
- regression: tests/upnp-renderer-regression.cjs 24/24, sabotage-verified

@sourcery-ai sourcery-ai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sorry @ErfanBagheri404, you've used your own review budget of 250,000 diff characters for the last 7 days.

You can request another review in 4 days and 11 hours by commenting @sourcery-ai review. Upgrade to get a review now.

@coderabbitai

coderabbitai Bot commented Sep 28, 2026 •

Copy link
Copy Markdown
Contributor

Warning

Review limit reached

You've used all free OSS reviews for now. Wait for the free limit to reset to keep reviewing this public repository.

Next included review available in 43 minutes.

Check out review usage here.

View limit details

Limit details: You’ve used the included review currently available.

Learn how review limits work.

Review configuration:

⚙️ Run configuration
  • Configuration used: defaults
  • Review profile: CHILL
  • Plan: Advanced
  • Run ID: c8fed823-4d11-4714-9e7d-182751343c02
📥 Commits

Reviewing files that changed from the base of the PR and between e421a1e and 5227778.

📒 Files selected for processing (2)
  • modules/upnpRenderer.ts
  • tests/upnp-renderer-regression.cjs
  • Autopilot · Keep fixing CodeRabbit findings and required CI, and resolving merge conflicts

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@sourcery-ai

sourcery-ai Bot commented Sep 28, 2026

Copy link
Copy Markdown

Reviewer's Guide

Implements the pure, testable UPnP/DLNA renderer-control half: SSDP/device-description parsing, DIDL-Lite queue metadata, and correctly routed SOAP transport/volume operations, with a 24-check standalone regression suite; multicast discovery remains intentionally out of scope.

Sequence diagram for UPnP renderer discovery parsing

sequenceDiagram
    participant SSDP as Native SSDP module
    participant Parser as Protocol module
    participant Device as Renderer device

    SSDP->>Parser: parseSsdpReply(raw)
    Parser->>Parser: usnDevice(usn)
    Parser->>Device: Read device description at location
    Device-->>Parser: Device description XML
    Parser->>Parser: parseDeviceDescription(xml)
    alt AVTransport service present
        Parser->>Parser: buildRenderer(reply, xml)
        Parser-->>SSDP: UpnpRenderer with absolute control URLs
    else AVTransport service absent
        Parser-->>SSDP: null
    end
Loading

Sequence diagram for UPnP queue playback and volume routing

sequenceDiagram
    participant Controller as Renderer controller
    participant Renderer as UPnP renderer
    participant AVTransport as AVTransport service
    participant RenderingControl as RenderingControl service

    Controller->>Controller: buildDidlWithIndex(track, index)
    Controller->>Controller: setAvTransportUri(track, index)
    Controller->>AVTransport: SetAVTransportURI
    Controller->>Controller: setNextAvTransportUri(track, index)
    Controller->>AVTransport: SetNextAVTransportURI
    Controller->>AVTransport: Play
    Controller->>AVTransport: GetPositionInfo
    AVTransport-->>Controller: RelTime
    Controller->>RenderingControl: SetVolume or GetVolume
    RenderingControl-->>Controller: volume response
Loading

File-Level Changes

Change Details Files
Added a dependency-free UPnP/DLNA wire-protocol module covering renderer discovery data, metadata generation, and SOAP control/parsing.
  • Parse SSDP replies and derive device prefixes for multi-service deduplication.
  • Parse device descriptions, resolve relative control URLs, and reject devices without AVTransport.
  • Generate escaped DIDL-Lite metadata with durations, protocol information, artwork, and indexed queue IDs.
  • Build SOAP actions and headers for transport and rendering control services.
  • Parse position times and renderer SOAP fault descriptions.
modules/upnpRenderer.ts
Added a standalone regression suite for the pure protocol implementation.
  • Transpile and load the TypeScript module without native dependencies.
  • Verify SSDP, device-description, URL-resolution, DIDL-Lite, SOAP construction, routing, parsing, clamping, and scope behavior.
  • Exercise the documented phone-reachable URL/native-discovery boundary through module checks.
tests/upnp-renderer-regression.cjs

Possibly linked issues


Tips and commands

Interacting with Sourcery

  • Trigger a new review: Comment @sourcery-ai review on the pull request.
  • Continue discussions: Reply directly to Sourcery's review comments.
  • Generate a GitHub issue from a review comment: Ask Sourcery to create an
    issue from a review comment by replying to it. You can also reply to a
    review comment with @sourcery-ai issue to create an issue from it.
  • Generate a pull request title: Write @sourcery-ai anywhere in the pull
    request title to generate a title at any time. You can also comment
    @sourcery-ai title on the pull request to (re-)generate the title at any time.
  • Generate a pull request summary: Write @sourcery-ai summary anywhere in
    the pull request body to generate a PR summary at any time exactly where you
    want it. You can also comment @sourcery-ai summary on the pull request to
    (re-)generate the summary at any time.
  • Generate reviewer's guide: Comment @sourcery-ai guide on the pull
    request to (re-)generate the reviewer's guide at any time.
  • Resolve all Sourcery comments: Comment @sourcery-ai resolve on the
    pull request to resolve all Sourcery comments. Useful if you've already
    addressed all the comments and don't want to see them anymore.
  • Dismiss all Sourcery reviews: Comment @sourcery-ai dismiss on the pull
    request to dismiss all existing Sourcery reviews. Especially useful if you
    want to start fresh with a new review - don't forget to comment
    @sourcery-ai review to trigger a new review!

Customizing Your Experience

Access your dashboard to:

  • Enable or disable review features such as the Sourcery-generated pull request
    summary, the reviewer's guide, and others.
  • Change the review language.
  • Add, remove or edit custom review instructions.
  • Adjust other review settings.

Getting Help

@llamapreview llamapreview Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LlamaPReview — No blocking issues found

Read bounded PR-head context from package.json.

Change flow

One renderer's request topology: the AVTransport gate is loud at the picker, but a missing RenderingControl is silent at the routing branch.

sequenceDiagram
participant S as SSDPReply
participant B as buildRenderer
participant P as Picker
participant R as controlUrlFor
participant D as Renderer
S->>B: LOCATION + USN + ST
B->>B: parseDeviceDescription
B->>B: has serviceType containing #35;58#59;AVTransport#35;58#59;?
alt no AVTransport
B-->>P: null — device never listed
else AVTransport present
B-->>P: renderer with two control URLs
P->>R: route action
note over B,R: PR change — AVTransport gate is enforced, routing branch can return an empty RenderingControl URL
opt SetVolume or GetVolume
R-->>D: renderingControlUrl — may be empty string
end
opt all other actions
R-->>D: AVTransport controlUrl
end
D-->>P: SOAP response or fault
end
Loading
Review details and evidence

Material unknowns

  • Is typescript declared in package.json and is this suite registered? The suite does require('typescript') at module load; if typescript is absent, node tests/upnp-renderer-regression.cjs throws before any check runs and the 24/24 claim fails as written. Deep judged this a documentation/evidence defect only — it changes confidence in the 24/24 claim, not the merge posture.
    • Check: Confirm typescript is in dependencies and wire the suite into a check that runs at PR head.
  • Executed green run of the suite at PR head. No exact-head execution output exists for the suite; the 24/24 claim rests only on the author's report. Corroborative, not decisive — the module is additive, wired to no caller, and no repository check runs this suite.
    • Check: Run node tests/upnp-renderer-regression.cjs at head and record the output.
  • No consumer references the new module. Searches for upnpRenderer returned no hits; this is a coverage gap, not proof of absence. The PR states the module is the first half, so this is a phasing fact rather than a defect.
    • Check: None required by Deep.

LlamaPReview checks

  • Read bounded PR-head context from package.json.
  • Reviewed changed regions in tests/upnp-renderer-regression.cjs.
  • Read the complete PR-head file .github/workflows/pr-checks.yml.
  • Reviewed changed regions in modules/upnpRenderer.ts.
  • Read bounded PR-head context from modules/upnpRenderer.ts.

LlamaPReview is an open-source pull request reviewer. See exactly what it will and will not publish.

soapActionHeader hardcoded AVTransport:1 / RenderingControl:1 regardless of
what the renderer advertised, so a device exposing AVTransport:2 rejected
every action even though the SOAP body was correct. buildRenderer already
read the real service types; they are now kept on the UpnpRenderer and fed
into soapActionHeader via the new actionUrn / serviceTypeFor helpers.
RenderingControl's version is stored independently — the two families
version separately.

Tests: 3 new regressions (v2 device advertised version, independent family
versions, no RenderingControl at all). 27/27 pass; sabotage mutations of
the version extraction and family selection all caught.

@sourcery-ai sourcery-ai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sourcery assessment

Approved.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

UPnP / DLNA device playback — cast to any network speaker

1 participant