Files
castarr/CONTEXT.md
be-nj f1a6600e42
All checks were successful
Build TV app / build (push) Successful in 2m54s
Emulator helper so tests start in seconds, not minutes
The Google TV emulator was launched by hand with -no-snapshot, which forces
a cold boot every time. tests/helpers/emulator.sh boots from the AVD's
default_boot snapshot (~7s) and refreshes that snapshot on stop, so the next
run stays fast. It also wraps the two things a test run needs afterwards:
installing an APK and pulling a screenshot into tests/runs/.

CONTEXT.md's example dialogue still described Xtream credentials entered
through the Remote, which ADR-0005 and ADR-0007 replaced with device flow and
phone-side onboarding.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-26 13:29:33 +02:00

3.4 KiB

Castarr

Native Kotlin app for Google TV that lets every user of a Dispatcharr backend watch live IPTV comfortably on their own TV: full D-pad UI, login via OIDC Device Flow (Authentik), plus an optional phone Remote paired via QR code.

Language

TV-App (Castarr): The native Kotlin app running on Google TV; full TV UI, plays streams, serves the Remote. Avoid: receiver, player app, NodeCast (old name)

Device Flow: OIDC device authorization grant: TV shows QR/code, user confirms on the phone at Authentik, app receives Bearer tokens for the Backend API. Avoid: pairing (that word is reserved for the Remote)

Backend (Dispatcharr): The self-hosted stream manager owning sources, aggregation, failover, EPG and stream profiles; forked at be-nj with native OIDC. Admin UI internal (), API external for TV clients (api.). Avoid: server (ambiguous)

Viewer (nodecast): The browser frontend for the family on — OIDC login, plays via its own transcode; consumes exactly one source, the Backend's M3U/EPG output (ADR-0006). Avoid: treating it as a second source of truth

Quelle (Source): A configured backend the TV-App reads channels from — primary type Dispatcharr fork (Bearer API), fallback type generic M3U+XMLTV without login (Threadfin, Tunarr, ErsatzTV).

Remote: The phone web UI served by the TV-App over HTTP; talks to the TV-App via WebSocket. Never talks to the Backend directly. Avoid: app (it is not an installed app), controller

Pairing: Trusting a Remote via the QR token (128-bit) or the rate-limited 4-digit code.

Channel: A playable live entry the Backend exposes via Xtream API or M3U. Avoid: station, sender (in code/docs)

Onboarding: First-run flow: the TV shows one QR (its own pairing server); server URL and the IdP login then happen entirely on the phone via the Remote. Nothing is ever typed on the TV.

Ten-Foot-Regel: In the family path the TV UI has no text fields and no scrolling except the channel list itself; every screen fits the screen. Text-heavy configuration lives in an "Erweitert" sub-screen.

Now/Next: The EPG scope of the Remote in phase 1: current + upcoming programme per Channel, no full guide timeline.

Relationships

  • The TV-App is a client of the Backend; the Remote only ever talks to the TV-App.
  • One User logs into one TV-App via Device Flow; favorites belong to the User on the Backend, not to the device.
  • A Remote controls exactly one TV-App; a TV-App accepts multiple paired Remotes.
  • Aggregation, failover and stream shaping happen in the Backend, not in the TV-App.

Example dialogue

Dev: "Does the Remote need Backend credentials?" Domain expert: "No — the Remote is where Onboarding happens, not where credentials live: the user types the server URL there and finishes the IdP login on the phone. The TV-App holds the tokens and is the only one talking to the Backend. A generic M3U Quelle needs no login at all."

Flagged ambiguities

  • "nativ" means: no Flutter/WebView wrapper — Kotlin app. It does not mean "no embedded web content" (the Remote is deliberately a web page).
  • "Passthrough" means the fork's raw stream profile (ffmpeg copy), selected per request via profile parameter — not a bypass of the Backend proxy.
  • Favorites are per User in the Dispatcharr fork (resolved; app-local and channel-group approaches were rejected).