Phase 6 Fix 2

-Wiedergabe Livestream und VOD im Desktopmodus
This commit is contained in:
Andre
2026-04-10 19:43:37 +02:00
parent 0c88bded01
commit ac9f9ea94f
6 changed files with 89 additions and 47 deletions
+17
View File
@@ -101,6 +101,23 @@ if [ -f "$NATIVE_NODE" ]; then
fi fi
fi fi
# ─────────────────────────────────────────────────────────────────────────────
# Schritt 4b: Python-Deps für streamlink via flatpak-pip-generator
# Erzeugt flatpak/python3-streamlink.json mit allen pip-Deps + sha256-Hashes.
# Die Datei wird von tv.twitch4steamdeck.App.yml per !include eingebunden.
# ─────────────────────────────────────────────────────────────────────────────
echo ""
echo "=== [4b] Python-Deps für streamlink generieren ==="
if ! python3 -c "import flatpak_pip_generator" &>/dev/null; then
echo " → Installiere flatpak-pip-generator ..."
# Ubuntu 24.04: externally-managed-environment → --break-system-packages nötig
pip3 install --quiet --break-system-packages flatpak-pip-generator
fi
python3 -m flatpak_pip_generator \
streamlink==6.11.0 \
--output flatpak/python3-streamlink
echo " ✓ flatpak/python3-streamlink.json generiert ($(wc -l < flatpak/python3-streamlink.json) Zeilen)"
# ───────────────────────────────────────────────────────────────────────────── # ─────────────────────────────────────────────────────────────────────────────
# Schritt 5: Manifest-SHA256-Werte füllen (interaktiv) # Schritt 5: Manifest-SHA256-Werte füllen (interaktiv)
# ───────────────────────────────────────────────────────────────────────────── # ─────────────────────────────────────────────────────────────────────────────
+20 -28
View File
@@ -64,14 +64,26 @@ modules:
sha256: 7f5da41b872302208f0e10c22d3e108ab9fdb1f253eb47c18651698c65d6b426 sha256: 7f5da41b872302208f0e10c22d3e108ab9fdb1f253eb47c18651698c65d6b426
# ───────────────────────────────────────────────────────────────────────────── # ─────────────────────────────────────────────────────────────────────────────
# 1c) mpv — Media-Player für Live-Streams und VODs # 1c) libXpresent — X11-Extension für VSync/Frame-Timing (mpv X11-Dependency)
# ffmpeg, libass, libplacebo, libva (VAAPI) verfügbar. # ─────────────────────────────────────────────────────────────────────────────
- name: libxpresent
buildsystem: autotools
sources:
- type: archive
url: https://xorg.freedesktop.org/releases/individual/lib/libXpresent-1.0.1.tar.xz
sha256: b964df9e5a066daa5e08d2dc82692c57ca27d00b8cc257e8e960c9f1cf26231b
# ─────────────────────────────────────────────────────────────────────────────
# 1d) mpv — Media-Player für Live-Streams und VODs
# ffmpeg, libass, libplacebo, libva (VAAPI), X11 verfügbar.
# ───────────────────────────────────────────────────────────────────────────── # ─────────────────────────────────────────────────────────────────────────────
- name: mpv - name: mpv
buildsystem: meson buildsystem: meson
config-opts: config-opts:
- -Dvaapi=enabled - -Dvaapi=enabled
- -Dpulse=enabled - -Dpulse=enabled
- -Dx11=enabled
- -Degl-x11=enabled
- -Dalsa=disabled - -Dalsa=disabled
- -Dlua=disabled - -Dlua=disabled
- -Djavascript=disabled - -Djavascript=disabled
@@ -85,33 +97,13 @@ modules:
# ───────────────────────────────────────────────────────────────────────────── # ─────────────────────────────────────────────────────────────────────────────
# 2) streamlink (Python) — liefert HLS-URLs aus Twitch-Streams # 2) streamlink (Python) — liefert HLS-URLs aus Twitch-Streams
# Python 3 kommt aus dem freedesktop SDK. # Wird von build-flatpak.sh via flatpak-pip-generator generiert:
# # python3 -m flatpak_pip_generator streamlink==6.11.0 \
# Vorbereitung (einmalig, in WSL2 Ubuntu): # --output flatpak/python3-streamlink
# pip3 install flatpak-pip-generator # Das erzeugt flatpak/python3-streamlink.json mit allen Deps + sha256-Hashes.
# python3 flatpak-pip-generator streamlink --output flatpak/python-streamlink-deps # flatpak-builder lädt alle Sources offline herunter und installiert sie sauber.
# → erzeugt flatpak/python-streamlink-deps.json mit allen pinned Deps + sha256.
# Dann dieses Modul hier durch die generierte Datei ersetzen:
# - name: python3-streamlink-deps
# buildsystem: simple
# build-commands: [...] # generiert durch flatpak-pip-generator
# sources: [...] # alle .whl/.tar.gz mit echten sha256
#
# Vereinfachte Variante (erfordert Netzwerk-Build — nur für lokale Tests):
# ───────────────────────────────────────────────────────────────────────────── # ─────────────────────────────────────────────────────────────────────────────
- name: python3-streamlink - !include python3-streamlink.json
buildsystem: simple
build-commands:
# --no-deps: Dependencies separat via flatpak-pip-generator auflösen
- pip3 install --prefix=/app --no-build-isolation --no-deps .
sources:
- type: archive
url: https://github.com/streamlink/streamlink/archive/refs/tags/6.11.0.tar.gz
# sha256 generieren: sha256sum streamlink-6.11.0.tar.gz
sha256: fbed984049b33f176e3a63e4bce302180d1ef24feffa24ea7306aad1a24bff40
# ACHTUNG: Vor dem echten Release-Build alle Python-Dependencies (requests,
# websocket-client, pycountry, pycryptodome, ...) mit flatpak-pip-generator
# als separate file-Sources einbinden. Sonst ist Streamlink zur Laufzeit unvollständig.
# ───────────────────────────────────────────────────────────────────────────── # ─────────────────────────────────────────────────────────────────────────────
# 3) Haupt-App # 3) Haupt-App
+34 -10
View File
@@ -24,20 +24,34 @@ export class PlaybackService extends EventEmitter {
if (this.current) await this.stop() if (this.current) await this.stop()
const proc = spawnStreamlink(`twitch.tv/${channelLogin}`, quality) const proc = spawnStreamlink(`twitch.tv/${channelLogin}`, quality)
this._attachLogs(proc)
let stderrBuf = ''
proc.stdout?.on('data', (d: Buffer) => console.log('[streamlink]', d.toString().trim()))
proc.stderr?.on('data', (d: Buffer) => {
const s = d.toString()
console.log('[streamlink stderr]', s.trim())
stderrBuf += s
})
proc.on('error', (e: Error) => { proc.on('error', (e: Error) => {
if (this.current?.process === proc) this.current = null if (this.current?.process === proc) this.current = null
this.emit('playback-event', { this.emit('playback-event', {
kind: 'error', kind: 'error',
message: `streamlink Fehler: ${e.message}` message: `streamlink nicht gefunden: ${e.message}`
} satisfies PlaybackEvent) } satisfies PlaybackEvent)
}) })
proc.on('exit', (code) => { proc.on('exit', (code) => {
if (this.current?.process === proc) this.current = null if (this.current?.process === proc) this.current = null
if (code !== 0 && code !== null) console.warn('[streamlink] exit code', code) if (code !== 0 && code !== null) {
const snippet = stderrBuf.trim().split('\n').slice(-6).join('\n').substring(0, 500)
this.emit('playback-event', {
kind: 'error',
message: `streamlink Exit ${code}:\n${snippet || '(kein Output)'}`
} satisfies PlaybackEvent)
} else {
this.emit('playback-event', { kind: 'stopped' } satisfies PlaybackEvent) this.emit('playback-event', { kind: 'stopped' } satisfies PlaybackEvent)
}
}) })
// Für Live: mpv wird von streamlink gestartet, kein direkter IPC nötig // Für Live: mpv wird von streamlink gestartet, kein direkter IPC nötig
@@ -75,20 +89,34 @@ export class PlaybackService extends EventEmitter {
} }
const proc = spawnMpv(hlsUrl, { ipcPath: this.ipcPath, startSeconds: resumePos }) const proc = spawnMpv(hlsUrl, { ipcPath: this.ipcPath, startSeconds: resumePos })
this._attachLogs(proc)
let mpvStderrBuf = ''
proc.stdout?.on('data', (d: Buffer) => console.log('[mpv]', d.toString().trim()))
proc.stderr?.on('data', (d: Buffer) => {
const s = d.toString()
console.log('[mpv stderr]', s.trim())
mpvStderrBuf += s
})
proc.on('error', (e: Error) => { proc.on('error', (e: Error) => {
if (this.current?.process === proc) this.current = null if (this.current?.process === proc) this.current = null
this.emit('playback-event', { this.emit('playback-event', {
kind: 'error', kind: 'error',
message: `mpv Fehler: ${e.message}` message: `mpv nicht gefunden: ${e.message}`
} satisfies PlaybackEvent) } satisfies PlaybackEvent)
}) })
proc.on('exit', (code) => { proc.on('exit', (code) => {
if (this.current?.process === proc) this.current = null if (this.current?.process === proc) this.current = null
if (code !== 0 && code !== null) console.warn('[mpv] exit code', code) if (code !== 0 && code !== null) {
const snippet = mpvStderrBuf.trim().split('\n').slice(-6).join('\n').substring(0, 500)
this.emit('playback-event', {
kind: 'error',
message: `mpv Exit ${code}:\n${snippet || '(kein Output)'}`
} satisfies PlaybackEvent)
} else {
this.emit('playback-event', { kind: 'stopped' } satisfies PlaybackEvent) this.emit('playback-event', { kind: 'stopped' } satisfies PlaybackEvent)
}
}) })
const mpv = new MpvController(this.ipcPath) const mpv = new MpvController(this.ipcPath)
@@ -129,8 +157,4 @@ export class PlaybackService extends EventEmitter {
this.stop().catch(() => {}) this.stop().catch(() => {})
} }
private _attachLogs(proc: ChildProcess): void {
proc.stdout?.on('data', (d: Buffer) => console.log('[player stdout]', d.toString().trim()))
proc.stderr?.on('data', (d: Buffer) => console.log('[player stderr]', d.toString().trim()))
}
} }
+11 -5
View File
@@ -35,9 +35,14 @@ const MPV_BIN = resolveMpvBin()
/** Startet streamlink so dass es mpv als Player verwendet (für Live-Streams). */ /** Startet streamlink so dass es mpv als Player verwendet (für Live-Streams). */
export function spawnStreamlink(url: string, quality: string): ChildProcess { export function spawnStreamlink(url: string, quality: string): ChildProcess {
return spawn(STREAMLINK_BIN, ['--player', MPV_BIN, '--player-args', '--fullscreen', url, quality], { // --vo=gpu: Legacy-OpenGL-Renderer (gpu-next/libplacebo scheitert in Flatpak ohne Vulkan)
stdio: ['ignore', 'pipe', 'pipe'] // --hwdec=auto: VAAPI-Fallback auf SW-Dekodierung wenn nötig
}) // --force-window=yes: Fenster auch bei vo-Init-Problemen erzwingen
return spawn(
STREAMLINK_BIN,
['--player', MPV_BIN, '--player-args', '--fullscreen --vo=gpu --hwdec=auto --force-window=yes', url, quality],
{ stdio: ['ignore', 'pipe', 'pipe'] }
)
} }
/** /**
@@ -70,12 +75,13 @@ export interface MpvOptions {
/** Startet mpv direkt mit einer URL (HLS oder lokal). IPC-Socket wird gesetzt. */ /** Startet mpv direkt mit einer URL (HLS oder lokal). IPC-Socket wird gesetzt. */
export function spawnMpv(url: string, { ipcPath, startSeconds }: MpvOptions): ChildProcess { export function spawnMpv(url: string, { ipcPath, startSeconds }: MpvOptions): ChildProcess {
const hwdec = process.platform === 'win32' ? 'auto' : 'vaapi'
const args = [ const args = [
url, url,
`--input-ipc-server=${ipcPath}`, `--input-ipc-server=${ipcPath}`,
'--fullscreen', '--fullscreen',
`--hwdec=${hwdec}` '--vo=gpu',
'--hwdec=auto',
'--force-window=yes'
] ]
if (startSeconds && startSeconds > 0) { if (startSeconds && startSeconds > 0) {
args.push(`--start=${startSeconds}`) args.push(`--start=${startSeconds}`)
+1 -1
View File
@@ -257,7 +257,7 @@ export default function ChannelScreen({ channel, onBack }: Props): JSX.Element {
)} )}
{playState === 'error' && ( {playState === 'error' && (
<p className="channel-screen__error">{errorMsg}</p> <p className="channel-screen__error" style={{ whiteSpace: 'pre-wrap', fontSize: '13px', maxHeight: '8em', overflowY: 'auto' }}>{errorMsg}</p>
)} )}
</div> </div>
</div> </div>
+4 -1
View File
@@ -126,8 +126,11 @@
- [x] `flatpak/build-flatpak.sh` Build-Skript für WSL2 (inkl. SHA256-Auflösung) - [x] `flatpak/build-flatpak.sh` Build-Skript für WSL2 (inkl. SHA256-Auflösung)
- [x] `resources/icons/icon.svg` App-Icon (Platzhalter) - [x] `resources/icons/icon.svg` App-Icon (Platzhalter)
- [x] mpv + streamlink als Flatpak-Module im Manifest definiert - [x] mpv + streamlink als Flatpak-Module im Manifest definiert
- [x] **Fix (Phase 6.1):** streamlink Python-Deps via `flatpak-pip-generator` eingebunden
(`build-flatpak.sh` Schritt 4b + `!include python3-streamlink.json` im Manifest)
- [x] **Fix (Phase 6.1):** mpv-Patch `patches/mpv-ffmpeg7-avio-const.patch` ins Manifest eingebunden
- [ ] **User:** WSL2-Setup (flatpak, flatpak-builder, Runtimes) — einmalig - [ ] **User:** WSL2-Setup (flatpak, flatpak-builder, Runtimes) — einmalig
- [ ] **User:** `bash flatpak/build-flatpak.sh` in WSL2 ausführen - [ ] **User:** `bash flatpak/build-flatpak.sh` in WSL2 neu ausführen (nach 6.1-Fix)
- [ ] **User:** Auf Steam Deck installieren und Login + Live + VOD durchspielen - [ ] **User:** Auf Steam Deck installieren und Login + Live + VOD durchspielen
- [ ] **User:** Als Non-Steam-Game in Gaming Mode hinzufügen, dort testen - [ ] **User:** Als Non-Steam-Game in Gaming Mode hinzufügen, dort testen