diff --git a/CLAUDE_PROGRESS.md b/CLAUDE_PROGRESS.md index 9eb6c3d..22a9b41 100644 --- a/CLAUDE_PROGRESS.md +++ b/CLAUDE_PROGRESS.md @@ -1,8 +1,10 @@ # Twitch4SteamDeck — Fortschritts-Snapshot -> Stand: 2026-04-07. Diese Datei dient als Übergabe zwischen Sessions. -> **Lies zuerst:** diese Datei + `todo.md`. Der vollständige Architekturplan liegt unter -> `C:\Users\andre\.claude\plans\smooth-shimmying-parasol.md` (im Repo gespiegelt als `plan.md`). +> Stand: **Ende 2026-04-07**. Diese Datei dient als Übergabe zwischen Sessions. +> **Lies zuerst:** diese Datei + `todo.md` + `plan.md`. +> Architekturplan-Quelle: `C:\Users\andre\.claude\plans\smooth-shimmying-parasol.md` (im Repo gespiegelt). +> +> **Aktueller Stand:** Phase 0–2 abgeschlossen & vom User verifiziert. Als nächstes Phase 3 (Live-Wiedergabe). --- @@ -53,63 +55,101 @@ - `gamepad.ts` — Gamepad-Polling → synthetische KeyboardEvents (DPad + linker Stick + A/B) - CSS komplett überarbeitet: `.app-layout` (Grid 260px / 1fr), `.sidebar`, `.screen`, `.card-grid` mit `grid-auto-rows: max-content`, `.card` mit Block-Layout - **Entfernt:** alte `HomeScreen.tsx` (ersetzt durch FollowingScreen) -- **Status:** noch nicht vom User getestet +- **Status:** ✅ End-to-End vom User verifiziert (2026-04-07). Karten-Grid sauber, Tastatur-Navigation funktioniert, Sidebar-Switching ok. -## Wo wir gerade stehen - -**Mitten in:** Phase 2 ist code-complete, wartet auf Verifikation durch den User. - -**Konkret offen:** -1. User muss eine Twitch-App auf https://dev.twitch.tv/console/apps registrieren - (Public Client, Redirect `http://localhost`) und die Client-ID in `.env` eintragen: - ``` - MAIN_VITE_TWITCH_CLIENT_ID= - ``` -2. `npm install` muss erneut laufen (neue Deps: `qrcode`, `@types/qrcode`). -3. `npm run dev` starten und den kompletten Login-Flow durchspielen: - - Button „Mit Twitch verbinden" → QR + Code erscheinen - - Auf Handy `twitch.tv/activate` öffnen, Code eingeben - - App sollte automatisch zu „Eingeloggt"-Platzhalter wechseln - - Neustart der App → sollte eingeloggt bleiben (Tokens in `userData/twitch-tokens.bin`) - -**Mögliche Stolpersteine, die morgen früh zu prüfen sind:** -- `import.meta.env.MAIN_VITE_TWITCH_CLIENT_ID` im Main-Prozess: setzt voraus, dass electron-vite die - `.env` lädt — falls nicht, alternativ `process.env.MAIN_VITE_TWITCH_CLIENT_ID` oder via - `define`-Plugin in `electron.vite.config.ts` injizieren. -- TypeScript könnte über `import.meta.env` im Main-Prozess meckern. tsconfig.node.json hat - `"types": ["node", "electron-vite/node"]` — sollte reichen. -- Renderer-Types: `src/renderer/src/types/t4sd.d.ts` muss von `tsconfig.web.json` erfasst werden - (ist über `src/renderer/**/*.ts` abgedeckt). +**Offener Rest aus Phase 2 (nicht blockierend):** +- Logout-Test (`userData/twitch-tokens.bin` wird gelöscht) — kommt später beim nächsten vollständigen Durchlauf +- Gamepad-Test mit echtem Steam-Deck-Controller steht noch aus (Dev auf Windows) --- -## Nächster Schritt morgen (in dieser Reihenfolge) +## Wo wir gerade stehen — Start Phase 3 -1. **Fragen:** „Hat der Login-Flow gestern durchgelaufen? Falls Fehler, welche Logs?" -2. **Falls Fehler:** Erst diese fixen, bevor neue Features dazukommen. - - Häufiger Verdächtiger: `import.meta.env`-Loading im Main → Workaround dokumentiert oben. -3. **Falls grün:** Direkt in **Schritt 2: Twitch Helix-Client + Followed-Channels-Home-Screen** einsteigen. - - Neue Dateien planen: `src/main/twitch/helixClient.ts`, `src/main/twitch/types.ts`, - IPC-Kanäle `twitch:get-followed`, `twitch:get-live-status`, - Renderer-Screen `src/renderer/src/screens/HomeScreen.tsx` mit Spatial-Navigation-Karten. - - Helix-Endpoints für MVP: `GET /users` (own), `GET /channels/followed`, `GET /streams` (batched). +**Ziel Phase 3:** Live-Wiedergabe werbefrei via `streamlink | mpv` starten, vom `FollowingScreen` aus +per Kanal-Karte → `ChannelScreen` (Detail) → Button „Live ansehen" → externes mpv-Fenster. + +### Was morgen zuerst passieren muss + +**1. Dev-Tooling verifizieren (User-Aktion nötig):** +Wir entwickeln auf Windows, deployen später nach Linux/Flatpak. Für Phase 3 muss der User lokal: +- `mpv` installieren: `winget install mpv` (danach `mpv --version` in neuer Shell prüfen) +- `streamlink` installieren: `winget install streamlink` oder `pip install streamlink` +- **Baseline-Test** ohne unseren Code: + ``` + streamlink --twitch-disable-ads twitch.tv/ best --player mpv + ``` + → öffnet mpv-Fenster mit dem Stream. Wenn das klappt, wissen wir, dass die Pipeline auf der + Windows-Dev-Umgebung grundsätzlich funktioniert und wir können im Code darauf aufbauen. + +**Wenn der Baseline-Test scheitert**, haben wir entweder: +- fehlende Binaries (nicht im PATH) +- Twitch hat `--twitch-disable-ads` gebrochen → Fallback auf `streamlink-ttvlol`-Plugin +- Region-/Geo-Blocking + +→ In diesem Fall **erst den Baseline fixen**, bevor Code geschrieben wird. + +**2. Architektur-Entscheidung bestätigen (kurz abfragen):** +- Direkter Sprung von Karte → mpv **oder** dazwischen `ChannelScreen` als Detail-View? +- Plan sagt: `ChannelScreen` mit Detail + „Live ansehen"-Button. Das ist auch sinnvoll, weil + in Phase 4 derselbe Screen die VOD-Liste hostet. Default: so umsetzen. +- Quality-Picker im MVP erstmal mit Fixed-Default `best`, UI-Dropdown nachziehen wenn Rest steht. + +**3. Dateien, die in Phase 3 neu entstehen (siehe `todo.md` Phase 3 für Details):** + +Main-Prozess: +- `src/main/playback/types.ts` +- `src/main/playback/streamlink.ts` — `spawnStreamlink({channelLogin, quality, oauthToken})` +- `src/main/playback/mpvController.ts` — Plattform-Weiche Named Pipe / Unix Socket, JSON-IPC +- `src/main/playback/playbackService.ts` — orchestriert streamlink + mpv, räumt auf +- Erweiterung `src/main/ipc/handlers.ts`: `playback:start-live`, `playback:stop`, `playback:event` + +Renderer: +- `src/renderer/src/screens/ChannelScreen.tsx` — Detail-Screen mit „Live ansehen"-Button +- Erweiterung `AppShell.tsx`: `selectedChannel`-State, Routing FollowingScreen ↔ ChannelScreen +- Erweiterung Preload + `t4sd.d.ts`: `window.t4sd.playback.{startLive, stop, onEvent}` +- `FocusableCard.onSelect` verkabeln (aktuell nur `console.log`) + +### Windows-spezifische Stolpersteine, die Claude morgen wissen muss + +- **mpv IPC-Socket:** auf Windows Named Pipe im Format `\\.\pipe\twitch4sd-mpv-`, NICHT + `/tmp/...sock`. In `mpvController.ts` mit `process.platform === 'win32'` verzweigen. +- **`net.createConnection` unter Windows mit Named Pipes:** funktioniert, erwartet aber den Pfad + als `{path: '\\\\.\\pipe\\twitch4sd-mpv-...'}`. +- **hwdec-Flag:** auf Windows `--hwdec=auto` oder `--hwdec=d3d11va`, nicht `vaapi` (das ist Linux). +- **Pipe streamlink → mpv:** funktioniert unter Windows mit Node `spawn` und `stdio: ['pipe', ...]` + und `streamlink.stdout.pipe(mpv.stdin)`. Oder alternativ: streamlink mit `--player mpv + --player-args "..."` aufrufen und mpv selbst die Pipe machen lassen. Variante 1 gibt uns mehr + Kontrolle, Variante 2 ist einfacher — **Vorschlag: mit Variante 2 starten**, später + refactoren falls wir mpv-IPC direkt aus unserem Code brauchen. +- **Achtung bei Variante 2:** mpv-IPC-Socket muss als `--player-args` durchgereicht werden, + z. B. `--player-args="--input-ipc-server=\\\\.\\pipe\\twitch4sd-mpv --fullscreen"`. +- **Prozess-Cleanup:** Beim App-Quit alle spawned Subprozesse killen (sonst Zombie-mpv-Fenster). + `app.on('before-quit', () => playbackService.stopAll())`. + +### Technische Baseline aus Phase 0–2 (Wiederholung für Sanity-Check) + +- Electron 33, electron-vite, TypeScript 5.5, React 18 +- Auth: `authService.getValidAccessToken()` liefert einen frischen OAuth-Token + (Auto-Refresh via Refresh-Token, 60s Safety-Margin) — für `--twitch-api-header` direkt nutzbar +- HelixClient liefert `FollowedChannelInfo[]` mit `broadcasterLogin`, was als Channel-Name für + `twitch.tv/` passt +- IPC-Pattern: `ipcMain.handle` im Main, `ipcRenderer.invoke` im Preload, `contextBridge` exponiert + `window.t4sd.*`. Main → Renderer Events via `webContents.send` + `ipcRenderer.on` + +### Wichtiges Projekt-Memo + +- Bei **allen Architekturänderungen** `plan.md` updaten (nicht nur `~/.claude/plans/...`). +- Nach **jeder Subaufgabe** Checkbox in `todo.md` setzen und 1-Zeilen-Update in dieser Datei. +- Dev-Host ist **Windows** (siehe Shell bash/Unix-Syntax in `/dev/null` etc.), Target ist + **Steam Deck (Linux / Flatpak)** — plattform-spezifische Weichen einbauen, wo es um Pfade, + Sockets und Hardware-Decoding geht. +- User akzeptiert „gut genug für MVP"-Entscheidungen (Quality-Picker später, kein Chat, etc.). + **Nicht** mit Speculative Abstractions aufblähen. --- -## Wie morgen effektiv weiterarbeiten? +## Erste Anweisung morgen an Claude (copy-paste) -**Empfehlung:** Beim Sessionstart **diese drei Dateien in dieser Reihenfolge** lesen lassen: - -1. `CLAUDE_PROGRESS.md` (diese Datei) — Snapshot -2. `todo.md` — Checkboxen-Tracker (jeder erledigte Schritt wird dort sofort abgehakt) -3. `plan.md` — gespiegelter Architekturplan (Quelle: `~/.claude/plans/smooth-shimmying-parasol.md`) - -**Workflow:** -- Vor jeder Subaufgabe: relevanten Abschnitt in `todo.md` checken. -- Nach Abschluss: Checkbox in `todo.md` setzen + 1-Zeilen-Update in `CLAUDE_PROGRESS.md` - unter „Bereits abgeschlossen". -- Bei Architekturänderungen: `plan.md` updaten (nicht nur die Kopie im `~/.claude/plans/`). - -**Erste Anweisung morgen an Claude (copy-paste):** -> „Lies CLAUDE_PROGRESS.md, todo.md und plan.md. Frag mich, ob der Login-Flow gestern durchgelaufen ist, -> und mach dann mit dem nächsten offenen Schritt weiter." +> „Lies CLAUDE_PROGRESS.md, todo.md und plan.md. Wir wollen Phase 3 (Live-Wiedergabe via +> streamlink + mpv) starten. Frag mich zuerst, ob ich streamlink und mpv lokal installiert habe +> und ob der Baseline-Test aus CLAUDE_PROGRESS.md funktioniert, dann leg los." diff --git a/plan.md b/plan.md index 9570177..a37633c 100644 --- a/plan.md +++ b/plan.md @@ -58,59 +58,73 @@ mit Resume. Verteilung als Flatpak. Wiedergabe läuft **außerhalb** Electron in einem mpv-Vollbildfenster. Electron startet/stoppt mpv, sendet Befehle, lauscht auf `time-pos` für Verlaufsspeicherung. -## Verzeichnisstruktur (Soll) +## Verzeichnisstruktur (Ist-Stand + Soll) ``` twitch4steamdeck/ -├── package.json -├── electron.vite.config.ts -├── electron-builder.yml +├── package.json ✅ +├── electron.vite.config.ts ✅ +├── electron-builder.yml ✅ +├── .env.example ✅ ├── flatpak/ -│ └── tv.twitch4steamdeck.App.yml +│ └── tv.twitch4steamdeck.App.yml ⏳ Phase 6 ├── src/ │ ├── main/ -│ │ ├── index.ts ✅ +│ │ ├── index.ts ✅ (Auth + Helix instanziiert) │ │ ├── auth/ -│ │ │ ├── deviceCodeFlow.ts ✅ -│ │ │ ├── tokenStore.ts ✅ -│ │ │ └── authService.ts ✅ +│ │ │ ├── deviceCodeFlow.ts ✅ +│ │ │ ├── tokenStore.ts ✅ +│ │ │ └── authService.ts ✅ │ │ ├── twitch/ -│ │ │ ├── helixClient.ts ⏳ Phase 2 -│ │ │ └── types.ts ⏳ Phase 2 +│ │ │ ├── helixClient.ts ✅ +│ │ │ └── types.ts ✅ │ │ ├── playback/ -│ │ │ ├── streamlink.ts ⏳ Phase 3 -│ │ │ └── mpvController.ts ⏳ Phase 3 +│ │ │ ├── streamlink.ts ⏳ Phase 3 +│ │ │ └── mpvController.ts ⏳ Phase 3 │ │ ├── store/ -│ │ │ ├── db.ts ⏳ Phase 5 -│ │ │ ├── historyRepo.ts ⏳ Phase 5 -│ │ │ └── settingsRepo.ts ⏳ Phase 5 +│ │ │ ├── db.ts ⏳ Phase 5 +│ │ │ ├── historyRepo.ts ⏳ Phase 5 +│ │ │ └── settingsRepo.ts ⏳ Phase 5 │ │ └── ipc/ -│ │ └── handlers.ts ✅ (nur Auth) +│ │ └── handlers.ts ✅ (Auth + twitch:get-followed) │ ├── preload/ -│ │ └── index.ts ✅ +│ │ └── index.ts ✅ (auth + twitch APIs) │ └── renderer/ -│ ├── index.html ✅ +│ ├── index.html ✅ │ └── src/ -│ ├── main.tsx ✅ -│ ├── App.tsx ✅ +│ ├── main.tsx ✅ +│ ├── App.tsx ✅ (Routing: LoginScreen / AppShell) │ ├── screens/ -│ │ ├── LoginScreen.tsx ✅ -│ │ ├── HomeScreen.tsx ⏳ Phase 2 -│ │ ├── ChannelScreen.tsx ⏳ Phase 3/4 +│ │ ├── LoginScreen.tsx ✅ +│ │ ├── AppShell.tsx ✅ (Sidebar + Main-Content, Region-Nav) +│ │ ├── FollowingScreen.tsx ✅ (ersetzt die ursprünglich geplante HomeScreen.tsx) +│ │ ├── BrowseScreen.tsx ✅ (Platzhalter) +│ │ ├── AccountScreen.tsx ✅ (Platzhalter + Logout) +│ │ ├── ChannelScreen.tsx ⏳ Phase 3/4 (Detail-Screen mit „Live ansehen") │ │ └── PlayerLaunchScreen.tsx ⏳ Phase 3 │ ├── components/ -│ │ ├── FocusableCard.tsx ⏳ Phase 2 -│ │ └── SpatialNav/ ⏳ Phase 2 +│ │ ├── Sidebar.tsx ✅ +│ │ ├── Icons.tsx ✅ (inline SVG: Heart/Compass/User) +│ │ └── FocusableCard.tsx ✅ (Block-Layout) │ ├── input/ -│ │ └── gamepad.ts ⏳ Phase 2 +│ │ └── gamepad.ts ✅ │ ├── types/ -│ │ └── t4sd.d.ts ✅ +│ │ └── t4sd.d.ts ✅ │ └── styles/ -│ └── global.css ✅ +│ └── global.css ✅ └── resources/ - └── icons/ + └── icons/ ⏳ ``` +**Architektur-Abweichungen vom Original-Plan (bewusst):** +- `HomeScreen.tsx` wurde durch `FollowingScreen.tsx` ersetzt und in ein `AppShell.tsx`-Layout + mit linker `Sidebar.tsx` (3 Tabs: Du folgst / Durchsuchen / Mein Account) eingebettet. +- Spatial-Navigation ist eine **eigene Implementierung** (Region-basiert: `sidebar` ↔ `main`), + kein `@noriginmedia/norigin-spatial-navigation`. Grund: einfacher, keine externe Dependency, + volle Kontrolle über das Region-Switch-Verhalten. +- `FocusableCard` nutzt **Block-Layout** (nicht Flex), weil Flex im Zusammenspiel mit + `aspect-ratio` und Grid-Zellen Schrumpf-Artefakte hatte. + ## Kernkomponenten — Spezifikation ### Auth (✅ implementiert) @@ -123,19 +137,54 @@ twitch4steamdeck/ - Client-ID via `MAIN_VITE_TWITCH_CLIENT_ID` (`.env` im Projektroot, electron-vite lädt sie). - Scope MVP: `user:read:follows`. -### Helix-Client (⏳ Phase 2) -MVP-Endpoints: -- `GET /users` (eigener User) -- `GET /channels/followed?user_id=…` (paginiert via `after`) +### Helix-Client (✅ implementiert) +Implementierte Endpoints (in `src/main/twitch/helixClient.ts`): +- `GET /users` (eigener User → `cachedUserId`) +- `GET /users?id=…` (batched, für Avatare der Followed-Kanäle) +- `GET /channels/followed?user_id=…` (paginiert via `after`, safety-cap 500) - `GET /streams?user_id=…` (batched, max 100 IDs/Call) -- `GET /videos?user_id=…&type=archive` +- ⏳ `GET /videos?user_id=…&type=archive` (noch offen für Phase 4) Header: `Client-Id: `, `Authorization: Bearer `. +`getFollowedWithLiveStatus()` merged Followed + Live + Avatare zu `FollowedChannelInfo[]` und +sortiert live-zuerst, dann alphabetisch. ### Streamlink + mpv (⏳ Phase 3) -- `streamlink --twitch-disable-ads --twitch-api-header=Authorization=OAuth --stdout twitch.tv/ best` -- Pipe an `mpv -` mit `--input-ipc-server=/tmp/twitch4sd-mpv.sock --fullscreen --hwdec=vaapi` -- Auf Windows: Named Pipe statt Unix-Socket. + +**Kommandos (Linux / Flatpak-Target):** +``` +streamlink \ + --twitch-disable-ads \ + --twitch-api-header=Authorization=OAuth \ + --stdout twitch.tv/ best + | mpv - \ + --input-ipc-server=/tmp/twitch4sd-mpv.sock \ + --fullscreen --hwdec=vaapi +``` + +**Windows-Entwicklung (Ist-Umgebung):** +- mpv: `winget install mpv` oder Binary von mpv.io ins PATH +- streamlink: `winget install streamlink` oder `pip install streamlink` +- IPC-Socket: Named Pipe `\\.\pipe\twitch4sd-mpv` (nicht `/tmp/...sock`) +- `--hwdec=vaapi` → auf Windows `--hwdec=d3d11va` oder `auto` +- Pipe stdout → stdin funktioniert auch unter Windows (Node `spawn` mit stdio pipe) + +**Architektur-Entscheidung für `mpvController`:** +- `process.platform === 'win32'` → Named Pipe path, sonst Unix Socket +- Nach `spawn` kurz warten (100–200 ms) bis der IPC-Endpoint bereit ist, dann `net.createConnection` +- JSON-IPC: pro Zeile ein Command `{"command": ["set_property", "pause", true]}\n` +- Property-Observer für `time-pos` → Events in Main-Prozess → IPC zum Renderer (für Phase 5 Verlauf) + +**Spezielle Streamlink-Flags zu prüfen:** +- `--twitch-low-latency` (reduziert Buffer, kann aber stottern verursachen) +- `--twitch-disable-hosting` +- TTV.LOL-Plugin: `pip install streamlink-ttvlol` — Fallback falls `--twitch-disable-ads` allein + keine Werbe-Umgehung mehr leistet. + +**IPC zwischen Renderer und Main (Phase 3):** +- `playback:start-live` → `{channelLogin, quality}` → startet Prozess-Kette, gibt `playbackId` zurück +- `playback:stop` → sendet `quit` an mpv, killt streamlink +- `playback:event` (main → renderer) → `{kind: 'started' | 'stopped' | 'error', message?}` ### VOD-History-DB (⏳ Phase 5) ```sql diff --git a/todo.md b/todo.md index ef36980..0c7eb7d 100644 --- a/todo.md +++ b/todo.md @@ -39,18 +39,57 @@ - [x] `AppShell.tsx` + `Sidebar.tsx` — Layout mit linker Navigation (Du folgst / Durchsuchen / Mein Account) - [x] `Icons.tsx` — inline SVG-Icons (Heart, Compass, User) - [x] `BrowseScreen.tsx` + `AccountScreen.tsx` — Platzhalter-Screens mit Logout -- [ ] **End-to-End-Verifikation durch User** (Sidebar-Navigation + Karten-Grid mit echten Daten testen) +- [x] **End-to-End-Verifikation durch User** (2026-04-07: Karten + Tastatur-Nav bestätigt) ## Phase 3 — Streamlink + mpv (Live-Wiedergabe) -- [ ] `src/main/playback/streamlink.ts` — Subprozess-Spawning, `--twitch-disable-ads`, - OAuth-Header für Sub-only Inhalte, optional TTV.LOL-Plugin -- [ ] `src/main/playback/mpvController.ts` — Spawn mpv mit `--input-ipc-server`, - Unix-Socket JSON-IPC (Win: Named Pipe), `pause`/`seek`/`quit`-Befehle, - `observe_property time-pos` -- [ ] IPC: `playback:start-live`, `playback:stop` -- [ ] Channel-Detail-Screen mit „Live ansehen"-Button -- [ ] Quality-Picker -- [ ] End-to-End: 5 min Live-Stream ohne Werbe-Unterbrechung + +### Vorab (Dev-Setup, Windows) +- [ ] **User:** `mpv` installieren und im PATH verfügbar machen (`winget install mpv` oder + Binary von mpv.io) — `mpv --version` muss in Shell funktionieren +- [ ] **User:** `streamlink` installieren (`winget install streamlink` oder + `pip install streamlink`) — `streamlink --version` muss funktionieren +- [ ] **User:** Kurz manuell testen: + `streamlink --twitch-disable-ads twitch.tv/ best --player mpv` + → soll ein mpv-Fenster mit Live-Stream öffnen. Wenn das klappt, ist der Pipeline-Baseline + verifiziert und wir können im Code darauf aufbauen. + +### Backend (Main-Prozess) +- [ ] `src/main/playback/types.ts` — `PlaybackState`, `PlaybackEvent`, `Quality` +- [ ] `src/main/playback/streamlink.ts` — `spawnStreamlink({channelLogin, quality, oauthToken})`, + gibt `ChildProcess` mit stdout-Stream zurück, `--twitch-disable-ads`, + `--twitch-api-header=Authorization=OAuth `, stderr in Logger +- [ ] `src/main/playback/mpvController.ts`: + - [ ] Plattform-Weiche: `win32` → Named Pipe `\\.\pipe\twitch4sd-mpv-`, + sonst Unix Socket unter `os.tmpdir()` + - [ ] `start(stdin)` — spawnt mpv mit `--input-ipc-server= --fullscreen --hwdec=auto -` + - [ ] IPC-Client verbindet mit Retry (100–200 ms Delay nach spawn) + - [ ] Commands: `pause(bool)`, `seek(sec)`, `quit()`, `observeProperty(name)` + - [ ] `EventEmitter`-Events: `'started'`, `'time-pos'`, `'ended'`, `'error'` +- [ ] `src/main/playback/playbackService.ts` — bindet streamlink + mpv zusammen, + verwaltet `currentPlayback: { streamlink, mpv, channelLogin }`, räumt beim Stop auf +- [ ] IPC-Kanäle (in `handlers.ts`): + - [ ] `playback:start-live` → `{channelLogin, quality}` → `Promise<{playbackId}>` + - [ ] `playback:stop` + - [ ] `playback:event` (main → renderer, `{kind, payload?}`) + +### Renderer (UI) +- [ ] Preload + `t4sd.d.ts` erweitern: `window.t4sd.playback.{startLive, stop, onEvent}` +- [ ] `src/renderer/src/screens/ChannelScreen.tsx` — Detail-Screen: + - [ ] Hero mit Thumbnail/Avatar + Kanalname + Titel + Spiel + Viewer + - [ ] Button „Live ansehen" (default `best`) + - [ ] Quality-Picker als Dropdown/Liste (`best`, `720p`, `480p`, `audio_only`) — MVP optional, + kann mit Default `best` beginnen und später nachgezogen werden + - [ ] „Zurück"-Button (B / Escape) +- [ ] Routing in `AppShell.tsx` erweitern: `selectedChannel: FollowedChannelInfo | null` State, + wenn gesetzt → `ChannelScreen` statt `FollowingScreen` anzeigen +- [ ] `FocusableCard.onSelect` → setzt `selectedChannel` +- [ ] Spatial-Nav: B-Taste / Escape im ChannelScreen → zurück zu FollowingScreen + +### Verifikation +- [ ] 5 min Live-Stream ohne Werbe-Unterbrechung auf Windows (Dev-Setup) +- [ ] Beim Stop: mpv-Fenster schließt sich sauber, streamlink-Prozess terminiert, + keine Zombie-Prozesse im Task-Manager +- [ ] Logout/Login und erneutes Starten funktioniert ohne App-Neustart ## Phase 4 — VOD-Browsing + -Wiedergabe - [ ] Helix `GET /videos?user_id=…&type=archive` (paginiert)