mirror of
https://github.com/Andre-Bzk/twitch4steamdeck.git
synced 2026-11-15 09:33:09 +01:00
feat: replace mpv with hls.js HTML5 video player (v0.4.0)
- Remove mpvController.ts and hlsTrimmer.ts entirely - Rewrite PlaybackService: streamlink --stream-url → HLS-URL → IPC event playback:hls-url - Add VideoPlayer.tsx (hls.js <video> wrapper, forwardRef with seek/seekTo/pause/play/stop/getCurrentTime) - Integrate VideoPlayer into ChannelScreen with direct videoRef calls (no IPC roundtrip for seek/pause) - Add global AppShell playback overlay for direct stream start (A-button in Browse/Category/StreamList) - Propagate onStartLive callback through BrowseScreen, CategoryScreen, StreamListScreen - Add CORS bypass for Twitch CDN (webRequest.onBeforeSendHeaders) and autoplay-policy switch - Update CSP: add media-src 'self' blob: for hls.js MediaSource API - Open chapter panel without stopping playback (pause + resume instead of stop/restart) - Drop IPC channels: seek, seekTo, resume, togglePause, getCurrentPosition, onTimeUpdate - Add IPC channel: playback:hls-url event with HlsUrlPayload
This commit is contained in:
@@ -0,0 +1,138 @@
|
||||
#!/usr/bin/env node
|
||||
/**
|
||||
* Testet die Backend-Playback-Pipeline: streamlink → HLS-URL → Manifest-Fetch → Segment-Fetch
|
||||
*
|
||||
* Usage:
|
||||
* node scripts/test-playback-pipeline.mjs twitch.tv/<kanal>
|
||||
* node scripts/test-playback-pipeline.mjs twitch.tv/videos/<vod-id>
|
||||
*/
|
||||
|
||||
import { spawn } from 'node:child_process'
|
||||
import { existsSync } from 'node:fs'
|
||||
import { join } from 'node:path'
|
||||
|
||||
const url = process.argv[2]
|
||||
if (!url) {
|
||||
console.error('Usage: node scripts/test-playback-pipeline.mjs twitch.tv/<kanal>')
|
||||
process.exit(1)
|
||||
}
|
||||
|
||||
function resolveStreamlinkBin() {
|
||||
if (process.platform === 'win32') {
|
||||
const candidates = [
|
||||
join(process.env.LOCALAPPDATA ?? '', 'Programs', 'Streamlink', 'bin', 'streamlink.exe'),
|
||||
join(process.env.ProgramFiles ?? '', 'Streamlink', 'bin', 'streamlink.exe'),
|
||||
join(process.env['ProgramFiles(x86)'] ?? '', 'Streamlink', 'bin', 'streamlink.exe'),
|
||||
]
|
||||
for (const p of candidates) {
|
||||
if (existsSync(p)) return p
|
||||
}
|
||||
}
|
||||
return 'streamlink'
|
||||
}
|
||||
|
||||
const STREAMLINK_BIN = resolveStreamlinkBin()
|
||||
|
||||
function pass(msg) { console.log(` ✓ PASS ${msg}`) }
|
||||
function fail(msg) { console.log(` ✗ FAIL ${msg}`) }
|
||||
function info(msg) { console.log(` → ${msg}`) }
|
||||
|
||||
async function run() {
|
||||
console.log(`\n=== Playback Pipeline Test ===`)
|
||||
console.log(`URL: ${url}`)
|
||||
console.log(`Streamlink: ${STREAMLINK_BIN}`)
|
||||
console.log(`Streamlink existiert: ${existsSync(STREAMLINK_BIN)}`)
|
||||
console.log('')
|
||||
|
||||
// Schritt 1: Streamlink aufrufen
|
||||
console.log('[1] Streamlink --stream-url aufrufen ...')
|
||||
const { rawOut, rawErr, code, hlsUrl } = await new Promise((resolve) => {
|
||||
const proc = spawn(STREAMLINK_BIN, ['--stream-url', url, 'best'], {
|
||||
stdio: ['ignore', 'pipe', 'pipe']
|
||||
})
|
||||
let out = ''
|
||||
let err = ''
|
||||
proc.stdout?.on('data', (d) => { out += d.toString() })
|
||||
proc.stderr?.on('data', (d) => { err += d.toString() })
|
||||
proc.on('error', (e) => resolve({ rawOut: '', rawErr: String(e), code: -1, hlsUrl: '' }))
|
||||
proc.on('exit', (exitCode) => {
|
||||
const lines = out.split('\n').map(l => l.trim())
|
||||
const hlsUrl = lines.filter(l => l.startsWith('https://')).at(-1) ?? ''
|
||||
resolve({ rawOut: out, rawErr: err, code: exitCode, hlsUrl })
|
||||
})
|
||||
})
|
||||
|
||||
info(`Exit-Code: ${code}`)
|
||||
info(`stdout (roh):\n${rawOut.trim() || '(leer)'}`)
|
||||
if (rawErr.trim()) info(`stderr: ${rawErr.trim().slice(0, 300)}`)
|
||||
|
||||
if (code !== 0) {
|
||||
fail(`streamlink Exit-Code ${code}`)
|
||||
process.exit(1)
|
||||
}
|
||||
if (!hlsUrl) {
|
||||
fail('Keine https://-URL in stdout gefunden')
|
||||
process.exit(1)
|
||||
}
|
||||
pass(`HLS-URL gefunden: ${hlsUrl.slice(0, 80)}...`)
|
||||
|
||||
// Schritt 2: Manifest fetchen
|
||||
console.log('\n[2] HLS-Manifest fetchen ...')
|
||||
let manifestText = ''
|
||||
try {
|
||||
const res = await fetch(hlsUrl, {
|
||||
headers: {
|
||||
'Origin': 'https://www.twitch.tv',
|
||||
'Referer': 'https://www.twitch.tv/'
|
||||
}
|
||||
})
|
||||
info(`HTTP Status: ${res.status}`)
|
||||
manifestText = await res.text()
|
||||
info(`Manifest (erste 200 Zeichen):\n${manifestText.slice(0, 200)}`)
|
||||
|
||||
if (!manifestText.startsWith('#EXTM3U')) {
|
||||
fail('Antwort ist kein valides m3u8 (fehlt #EXTM3U)')
|
||||
process.exit(1)
|
||||
}
|
||||
pass('#EXTM3U vorhanden — valides HLS-Manifest')
|
||||
} catch (e) {
|
||||
fail(`Manifest-Fetch fehlgeschlagen: ${e}`)
|
||||
process.exit(1)
|
||||
}
|
||||
|
||||
// Schritt 3: Erstes Segment erreichbar?
|
||||
console.log('\n[3] Erstes Segment erreichbar? ...')
|
||||
const baseUrl = hlsUrl.substring(0, hlsUrl.lastIndexOf('/') + 1)
|
||||
const segmentLine = manifestText.split('\n').find(l => l.trim() && !l.startsWith('#'))
|
||||
if (!segmentLine) {
|
||||
// Möglicherweise Master-Playlist → Sub-Playlist URL suchen
|
||||
const subPlaylistLine = manifestText.split('\n').find(l => l.trim().startsWith('https://'))
|
||||
if (subPlaylistLine) {
|
||||
info(`Master-Playlist erkannt — Sub-Playlist: ${subPlaylistLine.trim().slice(0, 80)}`)
|
||||
pass('Master-Playlist vorhanden (Sub-Playlists nicht weiter verfolgt)')
|
||||
} else {
|
||||
fail('Keine Segmente oder Sub-Playlists im Manifest gefunden')
|
||||
}
|
||||
} else {
|
||||
const segmentUrl = segmentLine.startsWith('https://') ? segmentLine.trim() : `${baseUrl}${segmentLine.trim()}`
|
||||
info(`Segment-URL: ${segmentUrl.slice(0, 80)}`)
|
||||
try {
|
||||
const segRes = await fetch(segmentUrl, { method: 'HEAD' })
|
||||
info(`Segment HTTP Status: ${segRes.status}`)
|
||||
if (segRes.ok) {
|
||||
pass(`Erstes Segment erreichbar (HTTP ${segRes.status})`)
|
||||
} else {
|
||||
fail(`Erstes Segment HTTP ${segRes.status}`)
|
||||
}
|
||||
} catch (e) {
|
||||
fail(`Segment-Fetch fehlgeschlagen: ${e}`)
|
||||
}
|
||||
}
|
||||
|
||||
console.log('\n=== Test abgeschlossen ===\n')
|
||||
}
|
||||
|
||||
run().catch((e) => {
|
||||
console.error('Unerwarteter Fehler:', e)
|
||||
process.exit(1)
|
||||
})
|
||||
Reference in New Issue
Block a user