Files
castarr/CONTEXT.md
benjamin 20e8eb1874
Some checks failed
Build TV app / build (push) Failing after 2s
0.2.2 + 0.3.0: remote threading fix, phone-first onboarding, ten-foot settings, self-healing errors, in-app updater, group filter
Squashed history rewrite: earlier revisions of the docs carried private
hostnames; placeholders throughout history now.

- Fix phone remote threading (welcome/get_state/broadcast) — remote connects
  and mirrors channels, Now/Next, favorites
- Phone-first onboarding (ADR-0007): one QR, server URL + IdP login on the
  phone (closes #11)
- Ten-foot settings + Erweitert sub-screen (closes #14), TvTextField D-pad
  focus fix (closes #9)
- EPG grid envelope parsing, Now/Next live (closes #10)
- Self-healing error states (closes #13), in-app updater (closes #12)
- Channel group filter chips fed by Dispatcharr groups

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-25 23:37:15 +02:00

88 lines
3.3 KiB
Markdown

# 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
(<admin-host>), API external for TV clients (api.<viewer-host>).
_Avoid_: server (ambiguous)
**Viewer (nodecast)**:
The browser frontend for the family on <viewer-host> — 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 user enters the Xtream credentials of a
> **Quelle** once via the Remote, the **TV-App** stores them and is the only
> one talking to the **Backend**."
## 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).