mirror of
https://github.com/Andre-Bzk/twitch4steamdeck.git
synced 2026-11-15 15:43:08 +01:00
style: translate all remaining German code comments to English
German comments had survived the earlier translation pass. Mixing languages in comments creates inconsistency and makes the codebase harder to read for non-German contributors.
This commit is contained in:
@@ -49,8 +49,8 @@ export class AuthService extends EventEmitter {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Startet den Device Code Flow. Liefert die Anzeigedaten sofort zurück
|
* Starts the Device Code Flow. Returns display data immediately
|
||||||
* und pollt im Hintergrund. Statuswechsel werden als 'auth-event' emittiert.
|
* and polls in the background. Status changes are emitted as 'auth-event'.
|
||||||
*/
|
*/
|
||||||
async startDeviceFlow(): Promise<DeviceFlowStartInfo> {
|
async startDeviceFlow(): Promise<DeviceFlowStartInfo> {
|
||||||
if (!this.isConfigured()) {
|
if (!this.isConfigured()) {
|
||||||
@@ -86,8 +86,8 @@ export class AuthService extends EventEmitter {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Liefert ein gültiges Access-Token, refresht falls nötig.
|
* Returns a valid access token, refreshing it if necessary.
|
||||||
* Wird vom Helix-Client benutzt.
|
* Used by the Helix client.
|
||||||
*/
|
*/
|
||||||
async getValidAccessToken(): Promise<string | null> {
|
async getValidAccessToken(): Promise<string | null> {
|
||||||
if (!this.tokens) return null
|
if (!this.tokens) return null
|
||||||
@@ -100,7 +100,7 @@ export class AuthService extends EventEmitter {
|
|||||||
await saveTokens(this.tokens)
|
await saveTokens(this.tokens)
|
||||||
return this.tokens.accessToken
|
return this.tokens.accessToken
|
||||||
} catch {
|
} catch {
|
||||||
// Refresh fehlgeschlagen → User muss sich neu einloggen.
|
// Refresh failed — user must log in again.
|
||||||
this.tokens = null
|
this.tokens = null
|
||||||
await clearTokens()
|
await clearTokens()
|
||||||
return null
|
return null
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
// Twitch OAuth 2.0 Device Code Flow.
|
// Twitch OAuth 2.0 Device Code Flow.
|
||||||
// Doku: https://dev.twitch.tv/docs/authentication/getting-tokens-oauth/#device-code-grant-flow
|
// Docs: https://dev.twitch.tv/docs/authentication/getting-tokens-oauth/#device-code-grant-flow
|
||||||
|
|
||||||
const DEVICE_ENDPOINT = 'https://id.twitch.tv/oauth2/device'
|
const DEVICE_ENDPOINT = 'https://id.twitch.tv/oauth2/device'
|
||||||
const TOKEN_ENDPOINT = 'https://id.twitch.tv/oauth2/token'
|
const TOKEN_ENDPOINT = 'https://id.twitch.tv/oauth2/token'
|
||||||
@@ -100,7 +100,7 @@ export async function pollForToken(opts: PollOptions): Promise<PollOutcome> {
|
|||||||
payload = json as ErrorResponse
|
payload = json as ErrorResponse
|
||||||
}
|
}
|
||||||
} catch {
|
} catch {
|
||||||
/* nicht-JSON Antwort, ignorieren */
|
/* non-JSON response, ignore */
|
||||||
}
|
}
|
||||||
const msg = (payload.message ?? '').toLowerCase()
|
const msg = (payload.message ?? '').toLowerCase()
|
||||||
|
|
||||||
|
|||||||
+8
-8
@@ -64,15 +64,15 @@ function createWindow(): BrowserWindow {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* CORS-Bypass für Twitch CDN und HLS-Streams.
|
* CORS bypass for Twitch CDN and HLS streams.
|
||||||
* hls.js im Renderer sendet Requests von localhost/file:// — Twitch CDN erwartet
|
* hls.js in the renderer sends requests from localhost/file:// — Twitch CDN expects
|
||||||
* Origin: https://www.twitch.tv. Wir setzen Origin + Referer auf ausgehenden Requests
|
* Origin: https://www.twitch.tv. We set Origin + Referer on outgoing requests
|
||||||
* und ergänzen CORS-Response-Header, damit Chromium die Antworten akzeptiert.
|
* and inject CORS response headers so Chromium accepts the responses.
|
||||||
*
|
*
|
||||||
* HLS-Cache-Unterdrückung: xhrSetup in hls.js setzt Cache-Control: no-store auf jedem
|
* HLS cache suppression: xhrSetup in hls.js sets Cache-Control: no-store on every
|
||||||
* XHR. Das löst einen CORS-Preflight aus (Cache-Control ist kein safelisted header).
|
* XHR. This triggers a CORS preflight (Cache-Control is not a safelisted header).
|
||||||
* Damit der Preflight durchkommt, muss onHeadersReceived auch ttvnw.net-Domains abdecken
|
* For the preflight to pass, onHeadersReceived must also cover ttvnw.net domains
|
||||||
* (Segment-CDN: *.j.cloudfront.hls.ttvnw.net — enthält nicht "cloudfront.net").
|
* (segment CDN: *.j.cloudfront.hls.ttvnw.net — does not contain "cloudfront.net").
|
||||||
*/
|
*/
|
||||||
function setupTwitchCors(): void {
|
function setupTwitchCors(): void {
|
||||||
const isTwitchCdn = (url: string): boolean =>
|
const isTwitchCdn = (url: string): boolean =>
|
||||||
|
|||||||
@@ -1,32 +1,32 @@
|
|||||||
/**
|
/**
|
||||||
* Liest Gamepad-Events via Linux evdev (/dev/input/event*).
|
* Reads gamepad events via Linux evdev (/dev/input/event*).
|
||||||
*
|
*
|
||||||
* Erkennt Controller über /dev/input/js* und öffnet deren zugehöriges
|
* Detects controllers via /dev/input/js* and opens their corresponding
|
||||||
* event*-Interface. Das evdev-Interface liefert standardisierte, benannte
|
* event* interface. The evdev interface provides standardized, named
|
||||||
* Button-Codes (BTN_SOUTH, BTN_NORTH, …) die über alle Controller-Typen
|
* button codes (BTN_SOUTH, BTN_NORTH, …) that are consistent across all
|
||||||
* (Xbox, PlayStation, Nintendo) und Verbindungsarten (USB, Bluetooth)
|
* controller types (Xbox, PlayStation, Nintendo) and connection methods
|
||||||
* konsistent sind – unabhängig davon, welchen Treiber der Kernel nutzt.
|
* (USB, Bluetooth), regardless of which driver the kernel uses.
|
||||||
*
|
*
|
||||||
* Damit entfällt das alte Problem mit der button-Nummerierung im Joystick-API,
|
* This eliminates the old button-numbering problem in the Joystick API,
|
||||||
* bei dem Xbox-One-Controller via Bluetooth (Share-Button verschiebt die
|
* where Xbox One controllers via Bluetooth (Share button shifts the
|
||||||
* Nummerierung) X-Button als Nummer 3 statt 2 lieferten.
|
* numbering) reported the X button as number 3 instead of 2.
|
||||||
*
|
*
|
||||||
* Unterstützt mehrere Gamepads gleichzeitig (Steam Deck + Bluetooth Xbox).
|
* Supports multiple gamepads simultaneously (Steam Deck + Bluetooth Xbox).
|
||||||
* Hotplug wird durch periodisches Scannen von /dev/input/ abgedeckt.
|
* Hotplug is handled by periodically scanning /dev/input/.
|
||||||
*
|
*
|
||||||
* BTN_*-Codes sind konsistent für alle Controller:
|
* BTN_* codes are consistent across all controllers:
|
||||||
* BTN_SOUTH (0x130) = A / Cross
|
* BTN_SOUTH (0x130) = A / Cross
|
||||||
* BTN_EAST (0x131) = B / Circle
|
* BTN_EAST (0x131) = B / Circle
|
||||||
* BTN_NORTH (0x133) = X / Square
|
* BTN_NORTH (0x133) = X / Square
|
||||||
* BTN_WEST (0x134) = Y / Triangle
|
* BTN_WEST (0x134) = Y / Triangle
|
||||||
* BTN_TL (0x136) = LB / L1
|
* BTN_TL (0x136) = LB / L1
|
||||||
* BTN_TR (0x137) = RB / R1
|
* BTN_TR (0x137) = RB / R1
|
||||||
* BTN_TL2 (0x138) = LT / L2 (als Button bei manchen Controllern)
|
* BTN_TL2 (0x138) = LT / L2 (as button on some controllers)
|
||||||
* BTN_TR2 (0x139) = RT / R2 (als Button bei manchen Controllern)
|
* BTN_TR2 (0x139) = RT / R2 (as button on some controllers)
|
||||||
* ABS_HAT0X (16) = D-Pad X-Achse
|
* ABS_HAT0X (16) = D-Pad X-axis
|
||||||
* ABS_HAT0Y (17) = D-Pad Y-Achse
|
* ABS_HAT0Y (17) = D-Pad Y-axis
|
||||||
* ABS_X/Y (0/1) = Linker Stick
|
* ABS_X/Y (0/1) = Left stick
|
||||||
* ABS_Z/RZ (2/5) = LT/RT als Analog-Achse
|
* ABS_Z/RZ (2/5) = LT/RT as analog axis
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import * as fs from 'node:fs'
|
import * as fs from 'node:fs'
|
||||||
@@ -35,8 +35,8 @@ import type { BrowserWindow } from 'electron'
|
|||||||
import { DEDUP_WINDOW_MS, HOTPLUG_SCAN_INTERVAL_MS, TRIGGER_THRESHOLD } from '../constants/input'
|
import { DEDUP_WINDOW_MS, HOTPLUG_SCAN_INTERVAL_MS, TRIGGER_THRESHOLD } from '../constants/input'
|
||||||
import log from 'electron-log/main'
|
import log from 'electron-log/main'
|
||||||
|
|
||||||
// evdev input_event auf 64-bit Linux:
|
// evdev input_event on 64-bit Linux:
|
||||||
// { u64 tv_sec (8 Bytes), u64 tv_usec (8 Bytes), u16 type (2), u16 code (2), s32 value (4) } = 24 Bytes
|
// { u64 tv_sec (8 bytes), u64 tv_usec (8 bytes), u16 type (2), u16 code (2), s32 value (4) } = 24 bytes
|
||||||
const INPUT_EVENT_SIZE = 24
|
const INPUT_EVENT_SIZE = 24
|
||||||
|
|
||||||
const EV_KEY = 0x01
|
const EV_KEY = 0x01
|
||||||
@@ -52,7 +52,7 @@ const BTN_TR = 0x137
|
|||||||
const BTN_TL2 = 0x138
|
const BTN_TL2 = 0x138
|
||||||
const BTN_TR2 = 0x139
|
const BTN_TR2 = 0x139
|
||||||
|
|
||||||
// ABS-Achsen-Codes
|
// ABS axis codes
|
||||||
const ABS_X = 0
|
const ABS_X = 0
|
||||||
const ABS_Y = 1
|
const ABS_Y = 1
|
||||||
const ABS_Z = 2 // LT / L2
|
const ABS_Z = 2 // LT / L2
|
||||||
@@ -84,8 +84,8 @@ interface AxisState {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Sucht das zugehörige evdev-Device (/dev/input/eventN) für ein
|
* Finds the corresponding evdev device (/dev/input/eventN) for a
|
||||||
* Joystick-Device (/dev/input/jsN) über den /sys-Dateisystem-Pfad.
|
* joystick device (/dev/input/jsN) via the /sys filesystem path.
|
||||||
*/
|
*/
|
||||||
function findEventDevice(jsDevicePath: string): string | null {
|
function findEventDevice(jsDevicePath: string): string | null {
|
||||||
try {
|
try {
|
||||||
@@ -223,9 +223,9 @@ class EvdevReader {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Fallback-Reader für den Fall, dass kein evdev-Device gefunden wird.
|
* Fallback reader used when no evdev device is found.
|
||||||
* Nutzt das alte Joystick-API (8-Byte-Events, Joystick-Nummern-Mapping).
|
* Uses the legacy Joystick API (8-byte events, numeric button mapping).
|
||||||
* Deckt Controller ab, die kein /sys-Event-Device-Mapping haben.
|
* Covers controllers that have no /sys event device mapping.
|
||||||
*/
|
*/
|
||||||
class JoystickFallbackReader {
|
class JoystickFallbackReader {
|
||||||
private stream: fs.ReadStream | null = null
|
private stream: fs.ReadStream | null = null
|
||||||
@@ -350,13 +350,13 @@ class JoystickFallbackReader {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Startet das Lesen aller /dev/input/js* Devices (evdev-Interface bevorzugt).
|
* Starts reading all /dev/input/js* devices (evdev interface preferred).
|
||||||
* Gibt eine Cleanup-Funktion zurück.
|
* Returns a cleanup function.
|
||||||
*/
|
*/
|
||||||
export function startGamepadReader(getWindow: () => BrowserWindow | null): () => void {
|
export function startGamepadReader(getWindow: () => BrowserWindow | null): () => void {
|
||||||
if (process.platform !== 'linux') return () => {}
|
if (process.platform !== 'linux') return () => {}
|
||||||
|
|
||||||
// Keyed by js-Device-Pfad
|
// Keyed by js device path
|
||||||
const readers = new Map<string, EvdevReader | JoystickFallbackReader>()
|
const readers = new Map<string, EvdevReader | JoystickFallbackReader>()
|
||||||
const lastKeyAt = new Map<string, number>()
|
const lastKeyAt = new Map<string, number>()
|
||||||
|
|
||||||
|
|||||||
@@ -32,8 +32,8 @@ function sortQualities(qualities: string[]): string[] {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Fragt streamlink nach allen verfügbaren Qualitätsstufen für eine URL (--json).
|
* Queries streamlink for all available quality levels for a URL (--json).
|
||||||
* Gibt eine sortierte Liste zurück; bei Fehler leeres Array.
|
* Returns a sorted list; empty array on error.
|
||||||
*/
|
*/
|
||||||
export function getAvailableQualities(url: string): Promise<string[]> {
|
export function getAvailableQualities(url: string): Promise<string[]> {
|
||||||
return new Promise((resolve) => {
|
return new Promise((resolve) => {
|
||||||
@@ -56,8 +56,8 @@ export function getAvailableQualities(url: string): Promise<string[]> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Fragt streamlink nach der direkten HLS-URL für einen Stream oder VOD.
|
* Queries streamlink for the direct HLS URL of a stream or VOD.
|
||||||
* Wird für Live-Streams (twitch.tv/<login>) und VODs (twitch.tv/videos/<id>) verwendet.
|
* Used for live streams (twitch.tv/<login>) and VODs (twitch.tv/videos/<id>).
|
||||||
*/
|
*/
|
||||||
export function getStreamUrl(url: string, quality: string): Promise<string> {
|
export function getStreamUrl(url: string, quality: string): Promise<string> {
|
||||||
return new Promise((resolve, reject) => {
|
return new Promise((resolve, reject) => {
|
||||||
|
|||||||
@@ -157,7 +157,7 @@ export class HelixClient {
|
|||||||
boxArtUrl: g.box_art_url.replace('{width}', '285').replace('{height}', '380')
|
boxArtUrl: g.box_art_url.replace('{width}', '285').replace('{height}', '380')
|
||||||
}))
|
}))
|
||||||
|
|
||||||
// Zuschauerzahlen: pro Kategorie die Top-100-Streams abrufen (parallel)
|
// Viewer counts: fetch the top 100 streams per category in parallel
|
||||||
const headers = await this.authHeaders()
|
const headers = await this.authHeaders()
|
||||||
const viewerCounts = await Promise.all(
|
const viewerCounts = await Promise.all(
|
||||||
games.map(async (g) => {
|
games.map(async (g) => {
|
||||||
|
|||||||
@@ -20,14 +20,14 @@ export default function App(): JSX.Element {
|
|||||||
return off
|
return off
|
||||||
}, [refresh])
|
}, [refresh])
|
||||||
|
|
||||||
// Browser Gamepad API (funktioniert im Desktop-Modus / Windows)
|
// Browser Gamepad API (works in desktop mode / Windows)
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (navigator.userAgent.toLowerCase().includes('linux')) return
|
if (navigator.userAgent.toLowerCase().includes('linux')) return
|
||||||
gamepadService.start()
|
gamepadService.start()
|
||||||
return () => gamepadService.stop()
|
return () => gamepadService.stop()
|
||||||
}, [])
|
}, [])
|
||||||
|
|
||||||
// Main-Process Gamepad Reader (liest /dev/input/js* direkt, funktioniert in Gaming Mode)
|
// Main-process gamepad reader (reads /dev/input/js* directly, works in Gaming Mode)
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
return window.t4sd.gamepad.onInput((key) => {
|
return window.t4sd.gamepad.onInput((key) => {
|
||||||
const target = document.activeElement ?? document.body
|
const target = document.activeElement ?? document.body
|
||||||
|
|||||||
@@ -588,7 +588,7 @@ export default function ChannelScreen({ channel, onBack }: Props): JSX.Element {
|
|||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{/* HTML5 Video Player — rendert innerhalb des Electron-Fensters */}
|
{/* HTML5 video player — renders inside the Electron window */}
|
||||||
{hlsPayload && (
|
{hlsPayload && (
|
||||||
<VideoPlayer
|
<VideoPlayer
|
||||||
ref={videoRef}
|
ref={videoRef}
|
||||||
|
|||||||
Reference in New Issue
Block a user