# 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).
