Fix Chromecast Integration on Stash app - #7220
Open
dude-stash wants to merge 6 commits into
Open
Conversation
Chromecast and similar devices fetch media themselves and cannot reach localhost, so the UI needs an address on this machine that another device on the LAN can use. The browser cannot enumerate the host's interfaces; the server can. LocalIPv4s lists reachable IPv4 addresses, excluding loopback, unspecified and link-local, with the preferred outbound address first. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01UKPckoYEN9Pq8RXosMtbKe
A cast device fetches the media itself, so the scene player cannot forward what it is playing. Two things make that fail. Every stream endpoint claims video/mp4 - the direct stream and the Matroska one included, deliberately, to keep browsers happy. A Chromecast takes that at face value and cannot decode the file, which is why casting an MKV connects and then hangs on the splash screen. pickCastSource reads the file's scanned container and codecs instead, and only offers the direct stream for H.264 with AAC-family audio in MP4. It judges the container by VideoFile.format rather than the extension, because MatchContainer reads the magic bytes at scan time precisely for the case where a Matroska file is named .mp4. The URL is also built from this browser's origin, usually localhost, which the device resolves to itself. rewriteCastUrl points it at an address the server reported. Signed query parameters survive, so authenticated instances keep working. Both are pure functions taking an injectable base URL. Nothing imports them yet. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01UKPckoYEN9Pq8RXosMtbKe
Loads Google's Cast Application Framework and holds this page's connection to a device: whether one is connected, connecting and disconnecting, and loading media onto it. The SDK announces itself once per page and setOptions re-triggers receiver discovery, so the configured context is resolved once and shared. The listeners are removed again on destroy - a disposed scene player must not keep reacting to the device. Nothing imports this yet. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01UKPckoYEN9Pq8RXosMtbKe
The Chromecast tech loaded whatever the browser was playing onto the device: usually the direct stream, labelled video/mp4 even when the file is Matroska, at a localhost address the device resolves to itself. It connected and then sat on the Cast splash screen with nothing to report. Drive the Cast Application Framework from the scene player instead, so the source is chosen for the device and the URL points somewhere it can reach. A scene that cannot be cast now says so instead of hanging. Nothing about the player changes: same button, same slot left of the fullscreen toggle, same vjs-chromecast-button class, and the same Cast glyph - inlined here, since it came from the stylesheet that ships with the package being removed. AirPlay still follows the Chromecast setting; separating them belongs with the AirPlay work. Removes @silvermine/videojs-chromecast, and with it the only dependency in the UI lockfile fetched from outside the npm registry. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01UKPckoYEN9Pq8RXosMtbKe
The manual explains the two requirements that look contradictory - open Stash at localhost, but the device is sent a LAN address - and why an MKV takes half a minute to start. docs/CHROMECAST.md records why casting is not a Video.js tech, for whoever touches this next. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01UKPckoYEN9Pq8RXosMtbKe
dude-stash
marked this pull request as ready for review
September 10, 2026 18:37
DogmaDragon
requested changes
Sep 10, 2026
docs/CHROMECAST.md wasn't the right place for implementation notes. The user manual section is reworded to plain sentences and no longer wraps mid-sentence. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Description
Chromecast doesn't work: you press Cast, the device connects, but the TV only shows the Cast logo and nothing plays, with no error.
Two reasons:
video/mp4even when the file is MKV. Chrome accepts that; a Chromecast cannot play MKV at all, so it just waits.http://localhost:9999. The Chromecast downloads the video itself, and for the Chromecast,localhostmeans the Chromecast.This is why the workarounds in the issue help some people and not others: casting from an IP fixes the address, DNS tricks fix the hostname, but neither fixes the MKV problem.
What this does:
Stash now talks to the Google Cast framework directly from the scene player, so it can choose what to send:
localhostwith a LAN address of this machine. The browser can't read network interfaces, so the server reports them in a newsystemStatus.localIPsfield.http://localhost, since the Cast SDK won't start anywhere else — only the media URL changes.Signed URLs from #6529 still work, so authentication isn't affected.
UI-wise, nothing changes — same button, same place, same icon, same setting.
@silvermine/videojs-chromecastis removed (Stash draws the button itself now), which also removes the only UI lockfile dependency that wasn't from npm — it was a GitHub tarball that fails behind a strict proxy, sopnpm installnow works there too.Known issue: Chrome draws its own Cast button in the video's top-left corner that casts the tab, not the video. That comes from the browser, not Stash —
disableRemotePlaybackdidn't hide it, so I left it for a follow-up.Next: TV-side player controls (play/pause/seek) and simultaneous browser+TV playback, then AirPlay (same localhost problem).
Related Issue
Fixes #4136.
Testing
pnpm run validate, so CI picks it up with no workflow change)make test: 34 packages, no failuresmake it: passgolangci-lintv2.11.4 (same version as CI): 0 issuespnpm run validate: biome, stylelint, tsc, format, tests all passManual testing with a real Chromecast device:
cast_sender.jsnot loadedScreenshots
No visual changes — the Cast button keeps the same icon, placement, and behavior from the user's perspective.
Checklist
AI Usage Disclosure
I used Claude Code to help implement this feature and write the documentation in this PR. I reviewed, tested, and take responsibility for all of the code and the manual Chromecast testing described above.
Additional Context
None.