mirror of
https://github.com/Andre-Bzk/twitch4steamdeck.git
synced 2026-11-15 09:33:09 +01:00
- 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
130 lines
4.0 KiB
TypeScript
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()
|
|
})
|