Files
twitch4steamdeck/src/main/index.ts
T
Andre 48ab6e551f 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
2026-05-10 08:33:07 +02:00

130 lines
4.0 KiB
TypeScript

import { app, BrowserWindow, shell, screen, session } from 'electron'
import { join } from 'node:path'
import { AuthService } from './auth/authService'
import { HelixClient } from './twitch/helixClient'
import { PlaybackService } from './playback/playbackService'
import { registerIpcHandlers } from './ipc/handlers'
import { startGamepadReader } from './input/gamepadReader'
const isDev = !app.isPackaged
// Erlaubt video.play() ohne vorangehende User-Geste (nötig, weil play() in hls.js-Callbacks
// aufgerufen wird, die asynchron weit nach dem ursprünglichen Click-Event stattfinden).
app.commandLine.appendSwitch('autoplay-policy', 'no-user-gesture-required')
function createWindow(): BrowserWindow {
const isLinux = process.platform === 'linux'
const { width, height } = isLinux
? screen.getPrimaryDisplay().workAreaSize
: { width: 1280, height: 800 }
const win = new BrowserWindow({
width,
height,
minWidth: 1024,
minHeight: 640,
show: false,
autoHideMenuBar: true,
backgroundColor: '#0e0e10',
webPreferences: {
preload: join(__dirname, '../preload/index.js'),
sandbox: false,
contextIsolation: true,
nodeIntegration: false
}
})
win.once('ready-to-show', () => {
if (isLinux) win.maximize()
win.show()
})
win.webContents.setWindowOpenHandler(({ url }) => {
shell.openExternal(url)
return { action: 'deny' }
})
if (isDev && process.env.ELECTRON_RENDERER_URL) {
win.loadURL(process.env.ELECTRON_RENDERER_URL)
} else {
win.loadFile(join(__dirname, '../renderer/index.html'))
}
return win
}
/**
* CORS-Bypass für Twitch CDN und HLS-Streams.
* hls.js im Renderer sendet Requests von localhost/file:// — Twitch CDN erwartet
* Origin: https://www.twitch.tv. Wir setzen Origin + Referer auf ausgehenden Requests
* und ergänzen CORS-Response-Header, damit Chromium die Antworten akzeptiert.
*/
function setupTwitchCors(): void {
const isTwitchCdn = (url: string): boolean =>
url.includes('twitchsvc.net') ||
url.includes('cloudfront.net') ||
url.includes('twitch.tv') ||
url.includes('twitch.amazon.com')
session.defaultSession.webRequest.onBeforeSendHeaders((details, callback) => {
if (isTwitchCdn(details.url)) {
details.requestHeaders['Origin'] = 'https://www.twitch.tv'
details.requestHeaders['Referer'] = 'https://www.twitch.tv/'
}
callback({ requestHeaders: details.requestHeaders })
})
session.defaultSession.webRequest.onHeadersReceived((details, callback) => {
if (isTwitchCdn(details.url)) {
const headers: Record<string, string[]> = {}
for (const [k, v] of Object.entries(details.responseHeaders ?? {})) {
headers[k.toLowerCase()] = Array.isArray(v) ? v : [v]
}
headers['access-control-allow-origin'] = ['*']
headers['access-control-allow-headers'] = ['*']
headers['access-control-allow-methods'] = ['GET, HEAD, OPTIONS']
callback({ responseHeaders: headers })
} else {
callback({})
}
})
}
app.whenReady().then(async () => {
setupTwitchCors()
const clientId = import.meta.env.MAIN_VITE_TWITCH_CLIENT_ID ?? ''
const auth = new AuthService(clientId)
await auth.init()
const helix = new HelixClient(clientId, async () => {
const token = await auth.getValidAccessToken()
if (!token) throw new Error('Kein gueltiges Twitch-Access-Token vorhanden')
return token
})
const playback = new PlaybackService()
registerIpcHandlers(auth, helix, playback)
const stopGamepad = startGamepadReader(() => BrowserWindow.getAllWindows()[0] ?? null)
app.on('before-quit', () => {
stopGamepad()
playback.stop()
})
if (!clientId) {
console.warn(
'[twitch4steamdeck] MAIN_VITE_TWITCH_CLIENT_ID nicht gesetzt — Login wird im UI als nicht konfiguriert angezeigt.'
)
}
createWindow()
app.on('activate', () => {
if (BrowserWindow.getAllWindows().length === 0) createWindow()
})
})
app.on('window-all-closed', () => {
if (process.platform !== 'darwin') app.quit()
})