diff --git a/src/renderer/src/components/ChapterPanel.tsx b/src/renderer/src/components/ChapterPanel.tsx new file mode 100644 index 0000000..b604534 --- /dev/null +++ b/src/renderer/src/components/ChapterPanel.tsx @@ -0,0 +1,81 @@ +import { useEffect, useRef } from 'react' +import { GamepadHintItem, GamepadPrompt } from './GamepadPrompt' +import { formatTimestamp } from '../lib/formatting' +import type { VodChapter, VodInfo } from '../types/t4sd' + +interface Props { + vod: VodInfo + chapters: VodChapter[] + focusedIndex: number + loading: boolean + /** True when the chapter panel is opened during active playback (seek vs start). */ + duringPlayback: boolean +} + +export function ChapterPanel({ vod, chapters, focusedIndex, loading, duringPlayback }: Props): JSX.Element { + const listRef = useRef(null) + + useEffect(() => { + if (!listRef.current) return + const item = listRef.current.children[focusedIndex] as HTMLElement | undefined + item?.scrollIntoView({ block: 'nearest', behavior: 'smooth' }) + }, [focusedIndex]) + + return ( +
+
+
+ Kapitel wählen + {vod.title} +
+

+ Navigieren + · + {duringPlayback ? 'Springen' : 'Starten'} + · + Schließen +

+ {loading &&

Lade Kapitel…

} + {!loading && chapters.length === 0 && ( +
+

Keine Kapitel gefunden.

+

+ {duringPlayback ? ( + <> + + + zum Fortsetzen. + + {' · '} + + + zum Anfang springen. + + + ) : ( + + + zum Abspielen. + + )} +

+
+ )} + {chapters.length > 0 && ( +
    + {chapters.map((ch, i) => ( +
  • + {i + 1} + {ch.gameName} + {formatTimestamp(ch.positionSeconds)} +
  • + ))} +
+ )} +
+
+ ) +} diff --git a/src/renderer/src/components/PlaybackOverlay.tsx b/src/renderer/src/components/PlaybackOverlay.tsx index 1dfb8d0..3b20b67 100644 --- a/src/renderer/src/components/PlaybackOverlay.tsx +++ b/src/renderer/src/components/PlaybackOverlay.tsx @@ -1,6 +1,10 @@ +// Video playback overlay rendered inside the Electron window. +// Responsibility: overlay UI only (seek bar, channel info, controls, hints). +// No screen navigation logic — that lives in the parent (AppShell / ChannelScreen). import { useCallback, useEffect, useRef, useState } from 'react' import { GamepadHintItem } from './GamepadPrompt' -import { ClapperboardIcon, EyeIcon, GamepadIcon, PauseIcon, PlayIcon, QualityIcon, StopIcon } from './Icons' +import { ClapperboardIcon, EyeIcon, GamepadIcon, PauseIcon, PlayIcon, StopIcon } from './Icons' +import { QualityPanel } from './QualityPanel' import { formatCount, formatTimestamp } from '../lib/formatting' import { DOUBLE_TAP_MS, OVERLAY_HIDE_DELAY_MS } from '../constants/playback' @@ -365,37 +369,14 @@ export function PlaybackOverlay({ )} {onOpenQuality && availableQualities !== undefined && availableQualities.length > 0 && ( -
- - {qualityPanelOpen && ( -
-
Qualität
-
    - {availableQualities.map((q, i) => ( -
  • { onChangeQuality?.(q); showOverlay() }} - > - {q} - {q === currentQuality && ✓} -
  • - ))} -
-
- )} -
+ { onOpenQuality(); showOverlay() }} + onChange={(q) => { onChangeQuality?.(q); showOverlay() }} + /> )} + {open && ( +
+
Qualität
+
    + {qualities.map((q, i) => ( +
  • onChange(q)} + > + {q} + {q === current && ✓} +
  • + ))} +
+
+ )} + + ) +} diff --git a/src/renderer/src/components/VideoPlayer.tsx b/src/renderer/src/components/VideoPlayer.tsx index a48a3cb..9203d9f 100644 --- a/src/renderer/src/components/VideoPlayer.tsx +++ b/src/renderer/src/components/VideoPlayer.tsx @@ -1,3 +1,6 @@ +// Technical hls.js wrapper — renders a