mirror of
https://github.com/Andre-Bzk/twitch4steamdeck.git
synced 2026-11-15 09:33:09 +01:00
Phase 6 Fix 2
-Wiedergabe Livestream und VOD im Desktopmodus
This commit is contained in:
@@ -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)
|
||||||
# ─────────────────────────────────────────────────────────────────────────────
|
# ─────────────────────────────────────────────────────────────────────────────
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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()))
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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}`)
|
||||||
|
|||||||
@@ -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>
|
||||||
|
|||||||
@@ -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
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user