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
+36 -12
View File
@@ -24,20 +24,34 @@ export class PlaybackService extends EventEmitter {
if (this.current) await this.stop()
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) => {
if (this.current?.process === proc) this.current = null
this.emit('playback-event', {
kind: 'error',
message: `streamlink Fehler: ${e.message}`
message: `streamlink nicht gefunden: ${e.message}`
} satisfies PlaybackEvent)
})
proc.on('exit', (code) => {
if (this.current?.process === proc) this.current = null
if (code !== 0 && code !== null) console.warn('[streamlink] exit code', code)
this.emit('playback-event', { kind: 'stopped' } satisfies PlaybackEvent)
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)
}
})
// 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 })
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) => {
if (this.current?.process === proc) this.current = null
this.emit('playback-event', {
kind: 'error',
message: `mpv Fehler: ${e.message}`
message: `mpv nicht gefunden: ${e.message}`
} satisfies PlaybackEvent)
})
proc.on('exit', (code) => {
if (this.current?.process === proc) this.current = null
if (code !== 0 && code !== null) console.warn('[mpv] exit code', code)
this.emit('playback-event', { kind: 'stopped' } satisfies PlaybackEvent)
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)
}
})
const mpv = new MpvController(this.ipcPath)
@@ -129,8 +157,4 @@ export class PlaybackService extends EventEmitter {
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). */
export function spawnStreamlink(url: string, quality: string): ChildProcess {
return spawn(STREAMLINK_BIN, ['--player', MPV_BIN, '--player-args', '--fullscreen', url, quality], {
stdio: ['ignore', 'pipe', 'pipe']
})
// --vo=gpu: Legacy-OpenGL-Renderer (gpu-next/libplacebo scheitert in Flatpak ohne Vulkan)
// --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. */
export function spawnMpv(url: string, { ipcPath, startSeconds }: MpvOptions): ChildProcess {
const hwdec = process.platform === 'win32' ? 'auto' : 'vaapi'
const args = [
url,
`--input-ipc-server=${ipcPath}`,
'--fullscreen',
`--hwdec=${hwdec}`
'--vo=gpu',
'--hwdec=auto',
'--force-window=yes'
]
if (startSeconds && startSeconds > 0) {
args.push(`--start=${startSeconds}`)