Fix: Streamlink auth header entfernt, mpv-Pfad explizit aufgelöst Livestream wird kann jetzt abgespielt werden
11 KiB
Twitch4SteamDeck — Architekturplan
Spiegel von
C:\Users\andre\.claude\plans\smooth-shimmying-parasol.md. Fortschritt steht intodo.md, aktueller Stand inCLAUDE_PROGRESS.md.
Ziel
Steam-Deck-App zur werbefreien Wiedergabe von Twitch-Livestreams und -VODs. Big-Screen-/10-Foot-UI, vollständig per Gamepad bedienbar. Lokaler VOD-Verlauf mit Resume. Verteilung als Flatpak.
Tech-Stack (entschieden)
| Bereich | Wahl |
|---|---|
| Sprache/Runtime | TypeScript + Electron (Main + Renderer) |
| UI | React + CSS, Spatial-Navigation für Gamepad |
| Wiedergabe | externer mpv-Prozess via JSON-IPC-Socket |
| Stream-Quelle / Ad-Bypass | Streamlink (Subprozess) + --twitch-disable-ads, optional TTV.LOL |
| Twitch-Auth | OAuth 2.0 Device Code Flow |
| Twitch-Daten | Helix-API (api.twitch.tv/helix) |
| Token-Storage | Electron safeStorage → userData/twitch-tokens.bin |
| Lokale Persistenz | SQLite via better-sqlite3 (VOD-Verlauf, Settings) |
| Packaging | electron-builder → Flatpak |
MVP-Scope
- Twitch-Login via Device Code Flow
- Followed Channels (Liste, Live-Indicator)
- Live-Stream-Wiedergabe werbefrei (Streamlink → mpv)
- VOD-Browsing pro Kanal
- VOD-Wiedergabe werbefrei
- Lokaler VOD-Verlauf + Resume + „Continue Watching"-Reihe
Bewusst nicht im MVP: Chat/Emotes, Suche, Kategorien, PiP, Themes.
Architektur
┌─────────────────────────────────────────────────┐
│ Electron Main Process (Node) │
│ - AuthService (Device Code Flow, Refresh) │
│ - TwitchApiClient (Helix) │
│ - StreamlinkRunner (spawn streamlink) │
│ - MpvController (spawn mpv, JSON-IPC) │
│ - HistoryStore (better-sqlite3) │
│ - SettingsStore │
└──────────────┬──────────────────────────────────┘
│ IPC (contextBridge / preload)
┌──────────────┴──────────────────────────────────┐
│ Electron Renderer (React + TS) │
│ - Screens: Login, Home, Channel, Player │
│ - SpatialNavigation (Gamepad-Fokus) │
│ - GamepadInputService │
└─────────────────────────────────────────────────┘
Wiedergabe läuft außerhalb Electron in einem mpv-Vollbildfenster.
Electron startet/stoppt mpv, sendet Befehle, lauscht auf time-pos für Verlaufsspeicherung.
Verzeichnisstruktur (Ist-Stand + Soll)
twitch4steamdeck/
├── package.json ✅
├── electron.vite.config.ts ✅
├── electron-builder.yml ✅
├── .env.example ✅
├── flatpak/
│ └── tv.twitch4steamdeck.App.yml ⏳ Phase 6
├── src/
│ ├── main/
│ │ ├── index.ts ✅ (Auth + Helix instanziiert)
│ │ ├── auth/
│ │ │ ├── deviceCodeFlow.ts ✅
│ │ │ ├── tokenStore.ts ✅
│ │ │ └── authService.ts ✅
│ │ ├── twitch/
│ │ │ ├── helixClient.ts ✅
│ │ │ └── types.ts ✅
│ │ ├── playback/
│ │ │ ├── types.ts ✅ Phase 3
│ │ │ ├── streamlink.ts ✅ Phase 3
│ │ │ ├── mpvController.ts ✅ Phase 3
│ │ │ └── playbackService.ts ✅ Phase 3
│ │ ├── store/
│ │ │ ├── db.ts ⏳ Phase 5
│ │ │ ├── historyRepo.ts ⏳ Phase 5
│ │ │ └── settingsRepo.ts ⏳ Phase 5
│ │ └── ipc/
│ │ └── handlers.ts ✅ (Auth + twitch:get-followed + playback:*)
│ ├── preload/
│ │ └── index.ts ✅ (auth + twitch APIs)
│ └── renderer/
│ ├── index.html ✅
│ └── src/
│ ├── main.tsx ✅
│ ├── App.tsx ✅ (Routing: LoginScreen / AppShell)
│ ├── screens/
│ │ ├── 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
│ │ └── PlayerLaunchScreen.tsx — entfällt (in ChannelScreen integriert)
│ ├── components/
│ │ ├── Sidebar.tsx ✅
│ │ ├── Icons.tsx ✅ (inline SVG: Heart/Compass/User)
│ │ └── FocusableCard.tsx ✅ (Block-Layout)
│ ├── input/
│ │ └── gamepad.ts ✅
│ ├── types/
│ │ └── t4sd.d.ts ✅
│ └── styles/
│ └── global.css ✅
└── resources/
└── icons/ ⏳
Architektur-Abweichungen vom Original-Plan (bewusst):
HomeScreen.tsxwurde durchFollowingScreen.tsxersetzt und in einAppShell.tsx-Layout mit linkerSidebar.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. FocusableCardnutzt Block-Layout (nicht Flex), weil Flex im Zusammenspiel mitaspect-ratiound Grid-Zellen Schrumpf-Artefakte hatte.
Kernkomponenten — Spezifikation
Auth (✅ implementiert)
- Endpoint
POST https://id.twitch.tv/oauth2/devicemitclient_id+scopes(space-sep). - Anzeige
user_code+verification_uri+ QR. - Polling
POST https://id.twitch.tv/oauth2/tokenmitgrant_type=urn:ietf:params:oauth:grant-type:device_code. - Refresh-Token via Electron
safeStorage. - Auto-Refresh wenn Helix 401.
- Client-ID via
MAIN_VITE_TWITCH_CLIENT_ID(.envim Projektroot, electron-vite lädt sie). - Scope MVP:
user:read:follows.
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 viaafter, safety-cap 500)GET /streams?user_id=…(batched, max 100 IDs/Call)- ⏳
GET /videos?user_id=…&type=archive(noch offen für Phase 4)
Header: Client-Id: <id>, Authorization: Bearer <access_token>.
getFollowedWithLiveStatus() merged Followed + Live + Avatare zu FollowedChannelInfo[] und
sortiert live-zuerst, dann alphabetisch.
Streamlink + mpv (⏳ Phase 3)
Kommandos (Linux / Flatpak-Target):
streamlink \
--twitch-disable-ads \
--twitch-api-header=Authorization=OAuth <token> \
--stdout twitch.tv/<channel> best
| mpv - \
--input-ipc-server=/tmp/twitch4sd-mpv.sock \
--fullscreen --hwdec=vaapi
Windows-Entwicklung (Ist-Umgebung):
- mpv:
winget install mpvoder Binary von mpv.io ins PATH - streamlink:
winget install streamlinkoderpip install streamlink - IPC-Socket: Named Pipe
\\.\pipe\twitch4sd-mpv(nicht/tmp/...sock) --hwdec=vaapi→ auf Windows--hwdec=d3d11vaoderauto- Pipe stdout → stdin funktioniert auch unter Windows (Node
spawnmit stdio pipe)
Architektur-Entscheidung für mpvController:
process.platform === 'win32'→ Named Pipe path, sonst Unix Socket- Nach
spawnkurz warten (100–200 ms) bis der IPC-Endpoint bereit ist, dannnet.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-adsallein keine Werbe-Umgehung mehr leistet.
IPC zwischen Renderer und Main (Phase 3):
playback:start-live→{channelLogin, quality}→ startet Prozess-Kette, gibtplaybackIdzurückplayback:stop→ sendetquitan mpv, killt streamlinkplayback:event(main → renderer) →{kind: 'started' | 'stopped' | 'error', message?}
VOD-History-DB (⏳ Phase 5)
CREATE TABLE vod_history (
vod_id TEXT PRIMARY KEY,
channel_login TEXT NOT NULL,
channel_display TEXT,
title TEXT,
thumbnail_url TEXT,
duration_seconds INTEGER,
resume_position_seconds INTEGER NOT NULL DEFAULT 0,
watched_at INTEGER NOT NULL,
completed INTEGER NOT NULL DEFAULT 0
);
CREATE INDEX idx_history_watched ON vod_history(watched_at DESC);
„Continue Watching": SELECT … WHERE completed=0 ORDER BY watched_at DESC LIMIT 20.
Complete-Threshold: resume_position / duration > 0.95.
Big-Screen-UI / Gamepad
- Spatial-Navigation-Lib oder eigene Mini-Implementierung.
- Fokus-Ring deutlich sichtbar.
- Schrift: min. 22px Body, 36px+ Headlines.
- Mapping: D-Pad/Stick = Navigation, A = Aktivieren, B = Zurück, Y = Refresh, Start = Menü, L1/R1 = Tab.
Flatpak (⏳ Phase 6)
- Runtime:
org.freedesktop.Platform//23.08,org.electronjs.Electron2.BaseApp - Module: app-bundle,
mpv,streamlink(pip), evtl.streamlink-ttvlol - Permissions:
--share=network,--share=ipc,--socket=wayland,--socket=fallback-x11,--socket=pulseaudio,--device=dri
Verifikation
End-to-End auf Steam Deck oder Linux-Box:
- Build:
npm install && npm run build && flatpak-builder --user --install build-dir flatpak/…yml - Login-Flow mit Device-Code + Smartphone
- Live-Stream 5+ Min ohne Werbe-Unterbrechung (Streamlink-Debug-Logs gegenchecken)
- VOD ansehen, mpv beenden, neu starten → springt an gespeicherte Position
- Komplette Bedienung nur mit Steam-Deck-Controller
- Im Gaming Mode als Non-Steam-Game starten und alles wiederholen
Offene Punkte (Post-MVP)
- Eigene
client_idim Build vor Release. - Chat / Emotes (BTTV/FFZ/7TV).
- Suche, Kategorien, Top-Streams.
- README + ToS-Disclaimer (Twitch-Werbeumgehung kann gegen ToS verstoßen).