Skip to content

Fix Chromecast Integration on Stash app - #7220

Open
dude-stash wants to merge 6 commits into
stashapp:developfrom
dude-stash:chromecast/5-docs
Open

Fix Chromecast Integration on Stash app#7220
dude-stash wants to merge 6 commits into
stashapp:developfrom
dude-stash:chromecast/5-docs

Conversation

@dude-stash

@dude-stash dude-stash commented Sep 10, 2026

Copy link
Copy Markdown
Contributor

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:

  1. Stash used the Video.js Chromecast tech. A tech is a player backend, so it sends the TV the same stream the browser is playing (Direct stream), and Stash reports that as video/mp4 even when the file is MKV. Chrome accepts that; a Chromecast cannot play MKV at all, so it just waits.
  2. The URL is http://localhost:9999. The Chromecast downloads the video itself, and for the Chromecast, localhost means 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:

  • Pick the stream from the real video and audio codecs, not from the MIME type. Direct stream only for H.264 + AAC in mp4, otherwise the original transcode, otherwise HLS.
  • Replace localhost with a LAN address of this machine. The browser can't read network interfaces, so the server reports them in a new systemStatus.localIPs field.
  • Keep the page on HTTPS or http://localhost, since the Cast SDK won't start anywhere else — only the media URL changes.
  • If a scene can't be cast, show an error instead of hanging.

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-chromecast is 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, so pnpm install now 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 — disableRemotePlayback didn'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

  • 29 new unit tests for stream selection and URL rewriting (added Vitest, since the UI had no JS test runner; it now runs as part of pnpm run validate, so CI picks it up with no workflow change)
  • make test: 34 packages, no failures
  • make it: pass
  • golangci-lint v2.11.4 (same version as CI): 0 issues
  • pnpm run validate: biome, stylelint, tsc, format, tests all pass
  • UI build: pass

Manual testing with a real Chromecast device:

  • MKV scene: plays on the TV (first seconds take a while due to the transcode)
  • H.264/AAC mp4 scene: plays with no transcode
  • Chromecast setting toggled off: no cast button shown, cast_sender.js not loaded
  • Control bar looks the same as before

Screenshots

No visual changes — the Cast button keeps the same icon, placement, and behavior from the user's perspective.

Checklist

  • I have read and understood the Contributing document.
  • I have read and understood the AI Usage Policy document.
  • I have made corresponding changes to the documentation (if applicable).

AI Usage Disclosure

  • I have used AI tools to assist with this pull request, and I have disclosed the tools and how I used them below.

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.

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
@DogmaDragon DogmaDragon added the noncompliance Doesn't follow the template or other guidelines. label Sep 10, 2026
@dude-stash
dude-stash marked this pull request as ready for review September 10, 2026 18:37
@dude-stash dude-stash changed the title Chromecast/5 docs Fix Chromecast Integration on Stash app Sep 10, 2026
@DogmaDragon DogmaDragon removed the noncompliance Doesn't follow the template or other guidelines. label Sep 10, 2026
Comment thread docs/CHROMECAST.md Outdated
Comment thread ui/v2.5/src/docs/en/Manual/Interface.md
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>
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.

Can't cast any video to Chromecast

3 participants