mirror of
https://github.com/Andre-Bzk/twitch4steamdeck.git
synced 2026-11-15 15:43:08 +01:00
Phase 3 abgeschlossen
Fix: Streamlink auth header entfernt, mpv-Pfad explizit aufgelöst Livestream wird kann jetzt abgespielt werden
This commit is contained in:
+113
-120
@@ -1,155 +1,148 @@
|
||||
# Twitch4SteamDeck — Fortschritts-Snapshot
|
||||
|
||||
> Stand: **Ende 2026-04-07**. Diese Datei dient als Übergabe zwischen Sessions.
|
||||
> Stand: **Ende 2026-04-07 (Session 2)**. 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).
|
||||
> **Aktueller Stand:** Phase 0–2 abgeschlossen & verifiziert. Phase 3 Code-complete, noch NICHT verifiziert (mpv PATH-Problem beim ersten Start).
|
||||
|
||||
---
|
||||
|
||||
## Bereits abgeschlossen
|
||||
|
||||
### ✅ Projekt-Skelett (Electron + Vite + React + TypeScript)
|
||||
- `package.json`, `tsconfig.json`, `tsconfig.node.json`, `tsconfig.web.json`
|
||||
- `electron.vite.config.ts`, `electron-builder.yml`, `.gitignore`
|
||||
- Main-Prozess Stub: `src/main/index.ts` (BrowserWindow 1280×800, dunkles Theme)
|
||||
- Preload-Stub: `src/preload/index.ts` (contextBridge → `window.t4sd`)
|
||||
- Renderer: `src/renderer/index.html`, `src/renderer/src/main.tsx`, `App.tsx`,
|
||||
`styles/global.css` (Big-Screen-Baseline)
|
||||
- **Status:** vom User bestätigt — `npm run dev` öffnet Fenster mit „Twitch4SteamDeck".
|
||||
### ✅ Phase 0 — Projekt-Skelett
|
||||
Siehe vorherige Sessions.
|
||||
|
||||
### ✅ Twitch OAuth Device Code Flow
|
||||
- `src/main/auth/deviceCodeFlow.ts` — `requestDeviceCode()`, `pollForToken()`, `refreshAccessToken()`.
|
||||
Endpoints: `https://id.twitch.tv/oauth2/device`, `https://id.twitch.tv/oauth2/token`.
|
||||
Polling respektiert `interval`, `slow_down`, `expired`, `denied`, `cancelled`.
|
||||
- `src/main/auth/tokenStore.ts` — Persistenz via Electron `safeStorage` (kein keytar/native module),
|
||||
Datei: `userData/twitch-tokens.bin`. Fallback unverschlüsselt mit `0o600` falls Encryption nicht verfügbar.
|
||||
- `src/main/auth/authService.ts` — `EventEmitter`, Methoden: `init()`, `startDeviceFlow()`,
|
||||
`cancelFlow()`, `logout()`, `getValidAccessToken()` (mit Auto-Refresh, 60s Safety-Margin).
|
||||
Scope MVP: `['user:read:follows']`. Emittiert `'auth-event'`.
|
||||
- `src/main/ipc/handlers.ts` — IPC-Kanäle: `auth:get-status`, `auth:start-device-flow`,
|
||||
`auth:cancel`, `auth:logout`, `auth:is-configured`, `auth:event` (main → renderer).
|
||||
- `src/preload/index.ts` — exponiert `window.t4sd.auth.{isConfigured, getStatus, startDeviceFlow,
|
||||
cancel, logout, onEvent}`.
|
||||
- `src/renderer/src/screens/LoginScreen.tsx` — Phasen `idle | starting | awaiting | error | not-configured`,
|
||||
QR-Code via `qrcode`-Lib, Countdown, Cancel-Button.
|
||||
- `src/renderer/src/App.tsx` — Statusbasiertes Routing (`logged-out` → LoginScreen,
|
||||
`logged-in` → Platzhalter mit Logout-Button).
|
||||
- `src/renderer/src/types/t4sd.d.ts` — globale Window-Typen (manuell synchron zu Preload).
|
||||
- `.env.example` — Vorlage für `MAIN_VITE_TWITCH_CLIENT_ID`.
|
||||
- **Status:** ✅ End-to-End vom User verifiziert (2026-04-07). Login, Token-Persistenz nach Neustart funktionieren. Logout noch nicht getestet (kommt später).
|
||||
### ✅ Phase 1 — Twitch OAuth Device Code Flow
|
||||
Siehe vorherige Sessions. End-to-End verifiziert 2026-04-07.
|
||||
|
||||
### ✅ Phase 2 — Twitch Helix-Client + Sidebar-Layout
|
||||
Siehe vorherige Sessions. End-to-End verifiziert 2026-04-07.
|
||||
|
||||
---
|
||||
|
||||
### ✅ Phase 2 — Twitch Helix-Client + Sidebar-Layout (2026-04-07, code-complete)
|
||||
- `src/main/twitch/types.ts`, `helixClient.ts` — Helix-Wrapper: eigener User, followed channels (paginiert), live streams (batched), Avatare
|
||||
- IPC `twitch:get-followed` in `handlers.ts`, `index.ts` (HelixClient-Instanz)
|
||||
- Preload + `t4sd.d.ts` um `window.t4sd.twitch.getFollowed` erweitert
|
||||
- **Sidebar-Layout:** `AppShell.tsx` als Post-Auth-Container mit `Sidebar.tsx` (Heart / Compass / User icons)
|
||||
- Tabs: "Du folgst" (Default), "Durchsuchen" (Placeholder), "Mein Account" (Placeholder + Logout)
|
||||
- Region-basierte Spatial-Navigation (`sidebar` ↔ `main`), Arrow-Left am linken Card-Grid-Rand springt in Sidebar
|
||||
- `FollowingScreen.tsx` — Karten-Grid, live-first Sortierung, [Y] Refresh
|
||||
- `FocusableCard.tsx` — **Block-Layout** (kein flex → kein overlap), Thumbnail/Avatar, LIVE-Badge, Viewer-Badge unten-links, Avatar+Name-Zeile, Titel, Spiel
|
||||
- `Icons.tsx` — inline SVG-Icons
|
||||
- `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:** ✅ End-to-End vom User verifiziert (2026-04-07). Karten-Grid sauber, Tastatur-Navigation funktioniert, Sidebar-Switching ok.
|
||||
### 🔄 Phase 3 — Streamlink + mpv (Live-Wiedergabe) — Code complete, nicht verifiziert
|
||||
|
||||
**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)
|
||||
#### Neu erstellte Dateien (Session 2, 2026-04-07):
|
||||
|
||||
**Main-Prozess:**
|
||||
- `src/main/playback/types.ts` — `PlaybackEvent`, `Quality`
|
||||
- `src/main/playback/streamlink.ts` — `spawnStreamlink()` mit `resolveStreamlinkBin()` (sucht
|
||||
`.exe` in `%LOCALAPPDATA%\Programs\Streamlink\bin\` usw., Fallback `streamlink`)
|
||||
- `src/main/playback/mpvController.ts` — `getMpvIpcPath()`, `MpvController` (Named Pipe Win32
|
||||
/ Unix Socket), connect mit Retry, `quit()`, `disconnect()`
|
||||
- `src/main/playback/playbackService.ts` — orchestriert streamlink + mpv, `startLive()`, `stop()`,
|
||||
`stopAll()`, emittiert `'playback-event'`
|
||||
- `src/main/ipc/handlers.ts` — erweitert um `playback:start-live`, `playback:stop`,
|
||||
`playback:event` (main→renderer); `registerIpcHandlers` nimmt jetzt `PlaybackService` als 3. Arg
|
||||
- `src/main/index.ts` — instanziiert `PlaybackService`, registriert
|
||||
`app.on('before-quit', () => playback.stopAll())`
|
||||
|
||||
**Preload + Typen:**
|
||||
- `src/preload/index.ts` — `window.t4sd.playback.{startLive, stop, onEvent}` hinzugefügt
|
||||
- `src/renderer/src/types/t4sd.d.ts` — `PlaybackEvent`-Typ + `playback`-API hinzugefügt
|
||||
|
||||
**Renderer:**
|
||||
- `src/renderer/src/screens/ChannelScreen.tsx` — Detail-Screen: Thumbnail/Avatar/Name/Titel/Spiel/
|
||||
Viewer, Button „▶ Live ansehen", Zustandsanzeige (idle/starting/playing/error),
|
||||
Escape/B → Stop oder Zurück
|
||||
- `src/renderer/src/screens/AppShell.tsx` — `selectedChannel`-State, Routing zu `ChannelScreen`,
|
||||
`onSelectChannel` an `FollowingScreen` weitergegeben
|
||||
- `src/renderer/src/screens/FollowingScreen.tsx` — `onSelectChannel`-Prop verkabelt,
|
||||
`onSelect` der Karte ruft jetzt `onSelectChannel(ch)` auf (nicht mehr nur `console.log`)
|
||||
- `src/renderer/src/styles/global.css` — `.channel-screen` + alle Unter-Klassen ergänzt
|
||||
|
||||
#### Architektur-Entscheidungen in Phase 3:
|
||||
- **Variante 2:** streamlink spawnt mpv selbst via `--player mpv --player-args "..."`.
|
||||
Node steuert nur den streamlink-Prozess direkt.
|
||||
- **mpv IPC** via Named Pipe (Win32) oder Unix Socket (Linux), verbunden nach `spawn` im Hintergrund
|
||||
(nicht blockierend, nicht kritisch für MVP). Wird für `quit()` beim Stop genutzt.
|
||||
- **`resolveStreamlinkBin()`** sucht die `.exe` unter bekannten Windows-Installationspfaden,
|
||||
da Electron den User-PATH nicht vollständig erbt.
|
||||
|
||||
---
|
||||
|
||||
## Wo wir gerade stehen — Start Phase 3
|
||||
## Offener Punkt beim Start morgen — mpv PATH-Problem
|
||||
|
||||
**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 passiert ist:
|
||||
Streamlink startet (ENOENT behoben via `resolveStreamlinkBin()`), aber streamlink
|
||||
kann mpv nicht finden, weil **mpv nicht im PATH ist, den streamlink von Electron erbt**.
|
||||
|
||||
### Was morgen zuerst passieren muss
|
||||
Fehlerbild: `[streamlink] exit code 1`, kein mpv-Fenster.
|
||||
|
||||
**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/<live-kanal> 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.
|
||||
### Wahrscheinlichste Lösung (zuerst probieren):
|
||||
**VS Code (und damit das Electron-Dev-Fenster) neu starten.** Nach dem `setx PATH ...`
|
||||
für mpv muss VS Code neu gestartet werden, damit es den neuen PATH erbt.
|
||||
|
||||
**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
|
||||
```
|
||||
1. VS Code schließen
|
||||
2. VS Code neu öffnen
|
||||
3. npm run dev
|
||||
4. Kanal auswählen → "Live ansehen"
|
||||
```
|
||||
|
||||
→ In diesem Fall **erst den Baseline fixen**, bevor Code geschrieben wird.
|
||||
### Falls das nicht reicht — Fallback:
|
||||
mpv-Pfad ebenfalls resolven wie streamlink. In `streamlink.ts` eine `resolveMpvBin()`-Funktion
|
||||
ergänzen und den vollen Pfad an `--player` übergeben:
|
||||
|
||||
**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.
|
||||
```typescript
|
||||
// In streamlink.ts hinzufügen:
|
||||
function resolveMpvBin(): string {
|
||||
if (process.platform === 'win32') {
|
||||
const candidates = [
|
||||
join(process.env.LOCALAPPDATA ?? '', 'Programs', 'mpv', 'mpv.exe'),
|
||||
// shinchiro winget installiert nach: %LOCALAPPDATA%\Microsoft\WinGet\Packages\...
|
||||
// Dort nach mpv.exe suchen wenn oben nicht gefunden
|
||||
]
|
||||
for (const p of candidates) {
|
||||
if (existsSync(p)) return p
|
||||
}
|
||||
}
|
||||
return 'mpv'
|
||||
}
|
||||
```
|
||||
|
||||
**3. Dateien, die in Phase 3 neu entstehen (siehe `todo.md` Phase 3 für Details):**
|
||||
Dann in `spawnStreamlink()`:
|
||||
```typescript
|
||||
'--player', resolveMpvBin(), // statt '--player', 'mpv'
|
||||
```
|
||||
|
||||
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`
|
||||
**Wichtig:** `where mpv` in der Shell ausführen um den exakten Pfad zu kennen, bevor
|
||||
du `resolveMpvBin()` implementierst.
|
||||
|
||||
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`)
|
||||
### Falls auch das nicht reicht:
|
||||
streamlink selbst via `--player-args` testen ohne `--input-ipc-server` (vereinfachter Test),
|
||||
um zu isolieren ob das Problem mpv-start oder IPC-Argument ist.
|
||||
|
||||
### Windows-spezifische Stolpersteine, die Claude morgen wissen muss
|
||||
---
|
||||
|
||||
- **mpv IPC-Socket:** auf Windows Named Pipe im Format `\\.\pipe\twitch4sd-mpv-<pid>`, 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())`.
|
||||
## Verifikations-Checkliste Phase 3 (noch offen)
|
||||
|
||||
### 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/<login>` 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.
|
||||
- [ ] 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 nach Stop
|
||||
- [ ] Logout/Login und erneutes Starten funktioniert ohne App-Neustart
|
||||
|
||||
---
|
||||
|
||||
## Erste Anweisung morgen an Claude (copy-paste)
|
||||
|
||||
> „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."
|
||||
> „Lies CLAUDE_PROGRESS.md, todo.md und plan.md. Phase 3 ist code-complete. Das mpv
|
||||
> PATH-Problem war der letzte offene Punkt — ich habe VS Code neugestartet (oder den
|
||||
> Fallback umgesetzt). Sag mir ob der Stream jetzt läuft."
|
||||
|
||||
---
|
||||
|
||||
## Technische Baseline (Sanity-Check)
|
||||
|
||||
- Electron 33, electron-vite, TypeScript 5.5, React 18
|
||||
- streamlink 8.2.1 installiert unter `%LOCALAPPDATA%\Programs\Streamlink\bin\streamlink.exe`
|
||||
- mpv v0.41.0 installiert, via `setx` zum PATH hinzugefügt (VS Code-Neustart nötig!)
|
||||
- IPC-Pattern: `ipcMain.handle` → `ipcRenderer.invoke` → `contextBridge` → `window.t4sd.*`
|
||||
- Main→Renderer Events via `webContents.send` + `ipcRenderer.on`
|
||||
|
||||
### Wichtiges Projekt-Memo
|
||||
- Bei **allen Architekturänderungen** `plan.md` updaten.
|
||||
- Nach **jeder Subaufgabe** Checkbox in `todo.md` setzen und Update hier.
|
||||
- Dev-Host: **Windows**. Target: **Steam Deck (Linux / Flatpak)**.
|
||||
- **Nicht** mit Speculative Abstractions aufblähen.
|
||||
|
||||
Reference in New Issue
Block a user