Some checks failed
Build TV app / build (push) Failing after 2s
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>
88 lines
3.3 KiB
Markdown
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).
|