Skip to content

feat(cast): Chromecast control channel (Play/Stop/Status) - #76

Open
susamn wants to merge 1 commit into
feature/cast-mdnsfrom
feature/cast-chromecast-channel
Open

feat(cast): Chromecast control channel (Play/Stop/Status)#76
susamn wants to merge 1 commit into
feature/cast-mdnsfrom
feature/cast-chromecast-channel

Conversation

@susamn

@susamn susamn commented Aug 27, 2026

Copy link
Copy Markdown
Owner

Stacked on the mDNS PR. Completes the Chromecast provider.

chromecast_channel.go — Cast channel protocol over TLS: CONNECT, LAUNCH default media receiver (CC1AD845), LOAD stream URL as LIVE audio/mpeg, GET_STATUS (receiver + media), STOP. Heartbeat PINGs answered inline.

  • Play: dial → launch → load
  • Stop: dial → receiver status → STOP sessionId (no-op if nothing running)
  • Status: dial → receiver status → media contentId (for re-attach matching)

TLS InsecureSkipVerify — Cast devices present a self-signed cert with no trustable CA; channel carries only playback-control messages. Documented at the dial site.

Tested against a scripted fake Cast receiver (real TLS listener, throwaway cert). No new dependencies.

After this lands, the Chromecast provider is fully functional; UI + CLI wiring is the next PR.

internal/cast/chromecast_channel.go implements the Cast channel protocol
over a TLS socket: platform CONNECT, LAUNCH of the default media receiver
(CC1AD845), CONNECT to its transport, LOAD of the stream URL as a LIVE
audio/mpeg source, plus GET_STATUS on the receiver and media namespaces
and STOP. Heartbeat PINGs are answered inline so a readUntil never stalls.

Wires the three remaining chromecastProvider methods:
- Play:   dial -> launch -> load
- Stop:   dial -> receiver status -> STOP sessionId (no-op if nothing running)
- Status: dial -> receiver status -> media contentId (for re-attach match)

TLS uses InsecureSkipVerify -- Cast devices present a self-signed cert
with no trustable CA, and the channel carries only playback-control
messages; documented at the dial site.

Tested against a scripted fake Cast receiver (real TLS listener, throwaway
self-signed cert): LAUNCH+LOAD sequence and the loaded contentId, Status
active/inactive, Stop issues STOP, PING interleave doesn't stall.
No new dependencies.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01JBJdG8Qenand5i16WT6BHG
@susamn
susamn force-pushed the feature/cast-chromecast-channel branch from 8044e1a to a546e20 Compare August 28, 2026 03:47
@susamn

susamn commented Aug 28, 2026

Copy link
Copy Markdown
Owner Author

Reworked the control channel after live testing against real devices:

  • launch is now a poll loop — a device that was asleep sends several RECEIVER_STATUS messages over ~10s (waking, switching input) before one carries the media-receiver transportId; the old code took the first and gave up. Now it GET_STATUS first (app may already be up), sends LAUNCH if not, and reads RECEIVER_STATUS until the transport appears.
  • LOAD failures are now errors — LOAD_FAILED / LOAD_CANCELLED / INVALID_REQUEST were being swallowed as success.
  • per-operation timeouts — Play 45s (device wake is slow), Stop/Status 15s, dial 8s — so a UI-passed context.Background() can't hang forever.
  • stopSession waits for the ack before closing the socket.
  • CONNECT carries origin: {} (some newer firmware is stricter).
  • fake-receiver test now mutex-guards its state (was racy).

Verified end to end: mpdtui -cast-to "<speaker>" → device loads the httpd stream (MEDIA_STATUS), -cast-stop → device stops + MPD outputs restored.

Note: one device here (a Nest Hub) silently drops TCP to its advertised Cast port 8009 from this machine — looks like AP client-isolation; 6 other devices work. Not a code issue.

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.

1 participant