diff --git a/src/main/constants/input.ts b/src/main/constants/input.ts new file mode 100644 index 0000000..3bd6cfe --- /dev/null +++ b/src/main/constants/input.ts @@ -0,0 +1,3 @@ +export const TRIGGER_THRESHOLD = 8 +export const DEDUP_WINDOW_MS = 40 +export const HOTPLUG_SCAN_INTERVAL_MS = 3_000 diff --git a/src/main/input/gamepadReader.ts b/src/main/input/gamepadReader.ts index f205ba8..3deeecc 100644 --- a/src/main/input/gamepadReader.ts +++ b/src/main/input/gamepadReader.ts @@ -32,6 +32,7 @@ import * as fs from 'node:fs' import * as path from 'node:path' import type { BrowserWindow } from 'electron' +import { DEDUP_WINDOW_MS, HOTPLUG_SCAN_INTERVAL_MS, TRIGGER_THRESHOLD } from '../constants/input' // evdev input_event auf 64-bit Linux: // { u64 tv_sec (8 Bytes), u64 tv_usec (8 Bytes), u16 type (2), u16 code (2), s32 value (4) } = 24 Bytes @@ -70,16 +71,10 @@ const BUTTON_MAP: Record = { } const STICK_THRESHOLD = 16384 // ~50% von 32767 -// Trigger-Schwellwert: Achsenwert > 0 (nicht gedrückt) bis ~255–1023 je Controller -const TRIGGER_THRESHOLD = 8 const REPEAT_INITIAL_MS = 400 const REPEAT_INTERVAL_MS = 150 // Trigger-Achsen sollen kein Auto-Repeat auslösen (300s-Sprünge nicht wiederholen) const NO_REPEAT_ABS_CODES = new Set([ABS_Z, ABS_RZ]) -const SCAN_INTERVAL_MS = 3000 -// Bluetooth-/Steam-Setups können dieselbe Eingabe über mehrere Devices liefern. -// Nahezu zeitgleiche identische Events werden global entdoppelt. -const DUPLICATE_EVENT_WINDOW_MS = 40 interface AxisState { key: string | null @@ -367,7 +362,7 @@ export function startGamepadReader(getWindow: () => BrowserWindow | null): () => const onKey = (key: string): void => { const now = Date.now() const lastAt = lastKeyAt.get(key) ?? 0 - if (now - lastAt < DUPLICATE_EVENT_WINDOW_MS) return + if (now - lastAt < DEDUP_WINDOW_MS) return lastKeyAt.set(key, now) const win = getWindow() @@ -427,7 +422,7 @@ export function startGamepadReader(getWindow: () => BrowserWindow | null): () => } scanDevices() - const timer = setInterval(scanDevices, SCAN_INTERVAL_MS) + const timer = setInterval(scanDevices, HOTPLUG_SCAN_INTERVAL_MS) return () => { clearInterval(timer) diff --git a/src/renderer/src/components/PlaybackOverlay.tsx b/src/renderer/src/components/PlaybackOverlay.tsx index c085ce4..1dfb8d0 100644 --- a/src/renderer/src/components/PlaybackOverlay.tsx +++ b/src/renderer/src/components/PlaybackOverlay.tsx @@ -2,6 +2,7 @@ import { useCallback, useEffect, useRef, useState } from 'react' import { GamepadHintItem } from './GamepadPrompt' import { ClapperboardIcon, EyeIcon, GamepadIcon, PauseIcon, PlayIcon, QualityIcon, StopIcon } from './Icons' import { formatCount, formatTimestamp } from '../lib/formatting' +import { DOUBLE_TAP_MS, OVERLAY_HIDE_DELAY_MS } from '../constants/playback' interface PlaybackOverlayProps { playState: 'playing' | 'paused' @@ -30,9 +31,6 @@ interface PlaybackOverlayProps { } -const HIDE_DELAY_MS = 5_000 -const DOUBLE_TAP_MS = 300 - export function PlaybackOverlay({ playState, durationSeconds, @@ -81,7 +79,7 @@ export function PlaybackOverlay({ const scheduleHide = useCallback(() => { clearHideTimer() - hideTimerRef.current = setTimeout(() => setVisible(false), HIDE_DELAY_MS) + hideTimerRef.current = setTimeout(() => setVisible(false), OVERLAY_HIDE_DELAY_MS) }, [clearHideTimer]) /** Call on any user interaction to show overlay and reset auto-hide timer. */ diff --git a/src/renderer/src/components/VideoPlayer.tsx b/src/renderer/src/components/VideoPlayer.tsx index d2a4330..7f5d7d0 100644 --- a/src/renderer/src/components/VideoPlayer.tsx +++ b/src/renderer/src/components/VideoPlayer.tsx @@ -1,5 +1,6 @@ import Hls from 'hls.js' import { forwardRef, useEffect, useImperativeHandle, useRef } from 'react' +import { POSITION_REPORT_INTERVAL_MS } from '../constants/playback' export interface VideoPlayerHandle { seek(delta: number): void @@ -24,8 +25,6 @@ interface Props { onTimeUpdate?: (seconds: number) => void } -const POSITION_REPORT_INTERVAL_MS = 5_000 - export const VideoPlayer = forwardRef( ({ hlsUrl, startPosition, isLive, vodId, durationSeconds, onPlaying, onPaused, onEnded, onError, onTimeUpdate }, ref) => { const videoRef = useRef(null) diff --git a/src/renderer/src/constants/input.ts b/src/renderer/src/constants/input.ts new file mode 100644 index 0000000..f4b0976 --- /dev/null +++ b/src/renderer/src/constants/input.ts @@ -0,0 +1,3 @@ +export const AXIS_THRESHOLD = 0.5 +export const REPEAT_INITIAL_MS = 400 +export const REPEAT_INTERVAL_MS = 150 diff --git a/src/renderer/src/constants/playback.ts b/src/renderer/src/constants/playback.ts new file mode 100644 index 0000000..f2b52a3 --- /dev/null +++ b/src/renderer/src/constants/playback.ts @@ -0,0 +1,3 @@ +export const OVERLAY_HIDE_DELAY_MS = 5_000 +export const DOUBLE_TAP_MS = 300 +export const POSITION_REPORT_INTERVAL_MS = 5_000 diff --git a/src/renderer/src/constants/ui.ts b/src/renderer/src/constants/ui.ts new file mode 100644 index 0000000..bf18d26 --- /dev/null +++ b/src/renderer/src/constants/ui.ts @@ -0,0 +1,4 @@ +export const CARD_W = 260 +export const CARD_GAP = 16 +export const STREAM_W = 220 +export const STREAM_GAP = 14 diff --git a/src/renderer/src/input/gamepad.ts b/src/renderer/src/input/gamepad.ts index 1e647d8..63e7872 100644 --- a/src/renderer/src/input/gamepad.ts +++ b/src/renderer/src/input/gamepad.ts @@ -20,6 +20,8 @@ * Axis 0 (left stick X) → Left/Right */ +import { AXIS_THRESHOLD, REPEAT_INITIAL_MS, REPEAT_INTERVAL_MS } from '../constants/input' + const BUTTON_MAP: Record = { 0: 'Enter', // A 1: 'Escape', // B @@ -35,10 +37,6 @@ const BUTTON_MAP: Record = { 15: 'ArrowRight' } -const AXIS_THRESHOLD = 0.5 -const REPEAT_INITIAL_MS = 400 -const REPEAT_INTERVAL_MS = 150 - interface AxisRepeat { key: string | null heldSince: number diff --git a/src/renderer/src/screens/BrowseScreen.tsx b/src/renderer/src/screens/BrowseScreen.tsx index 99c5541..cf147bc 100644 --- a/src/renderer/src/screens/BrowseScreen.tsx +++ b/src/renderer/src/screens/BrowseScreen.tsx @@ -3,6 +3,7 @@ import { GamepadHintItem } from '../components/GamepadPrompt' import LanguageBadge from '../components/LanguageBadge' import type { FollowedChannelInfo, GameInfo } from '../types/t4sd' import { formatViewers, formatViewersFull } from '../lib/formatting' +import { STREAM_GAP, STREAM_W } from '../constants/ui' interface Props { hasFocus: boolean @@ -15,8 +16,6 @@ interface Props { type LoadState = 'loading' | 'ok' | 'error' type FocusRegion = 'shelf' | 'grid' -const STREAM_W = 220 -const STREAM_GAP = 14 export default function BrowseScreen({ hasFocus, diff --git a/src/renderer/src/screens/ChannelScreen.tsx b/src/renderer/src/screens/ChannelScreen.tsx index 4efd2c7..688c748 100644 --- a/src/renderer/src/screens/ChannelScreen.tsx +++ b/src/renderer/src/screens/ChannelScreen.tsx @@ -13,6 +13,7 @@ import { formatViewers, formatWatchedAt } from '../lib/formatting' +import { CARD_GAP, CARD_W } from '../constants/ui' interface Props { channel: FollowedChannelInfo @@ -21,9 +22,6 @@ interface Props { type FocusRegion = 'hero' | 'shelf' | 'chapters' -const CARD_W = 260 -const CARD_GAP = 16 - function resolveThumbnail(url: string | undefined, w = 1280, h = 720): string | undefined { return url?.replace('{width}', String(w)).replace('{height}', String(h)) }