import React, { useEffect, useRef, useState } from 'react' import Hls from 'hls.js' import { Play, Pause, SkipBack, SkipForward, Volume2, VolumeX, List, BookmarkPlus, Moon, X, ChevronLeft } from 'lucide-react' import { usePlayerStore } from '../../store/playerStore' import { createBookmark } from '../../api/me' import CoverImage from '../common/CoverImage' import { coverUrl } from '../../api/items' import ChapterList from './ChapterList' export default function AudioPlayer() { const { item, session, currentTime, duration, isPlaying, playbackRate, volume, chapters, sleepTimerActive, sleepTimer, setPlaying, setCurrentTime, setDuration, setPlaybackRate, setVolume, seek, stop, setExpanded, setSleepTimer, cancelSleepTimer, syncProgress, } = usePlayerStore() const audioRef = useRef(null) const hlsRef = useRef(null) const [showChapters, setShowChapters] = useState(false) const [showSpeedMenu, setShowSpeedMenu] = useState(false) const [showSleepMenu, setShowSleepMenu] = useState(false) const [muted, setMuted] = useState(false) const meta = item?.media?.metadata || {} const title = meta.title || item?.relPath || '' const author = meta.authors?.[0]?.name || '' // HLS laden sobald sich die Session ändert useEffect(() => { if (!session || !audioRef.current) return const hlsUrl = session.audioTracks?.[0]?.contentUrl if (!hlsUrl) return if (hlsRef.current) { hlsRef.current.destroy(); hlsRef.current = null } const audio = audioRef.current if (Hls.isSupported()) { const hls = new Hls({ startPosition: session.currentTime || 0 }) hls.loadSource(hlsUrl) hls.attachMedia(audio) hlsRef.current = hls } else if (audio.canPlayType('application/vnd.apple.mpegurl')) { audio.src = hlsUrl audio.currentTime = session.currentTime || 0 } audio.playbackRate = playbackRate audio.volume = volume audio.play().catch(() => {}) return () => { if (hlsRef.current) { hlsRef.current.destroy(); hlsRef.current = null } } }, [session?.id]) // isPlaying <-> audio useEffect(() => { if (!audioRef.current) return if (isPlaying) audioRef.current.play().catch(() => {}) else audioRef.current.pause() }, [isPlaying]) useEffect(() => { if (audioRef.current) audioRef.current.playbackRate = playbackRate }, [playbackRate]) useEffect(() => { if (audioRef.current) audioRef.current.volume = muted ? 0 : volume }, [volume, muted]) // Keyboard shortcuts useEffect(() => { const handler = (e: KeyboardEvent) => { if (e.target instanceof HTMLInputElement) return if (e.code === 'Space') { e.preventDefault(); setPlaying(!isPlaying) } if (e.code === 'ArrowRight') audioRef.current && (audioRef.current.currentTime += 30) if (e.code === 'ArrowLeft') audioRef.current && (audioRef.current.currentTime -= 10) } window.addEventListener('keydown', handler) return () => window.removeEventListener('keydown', handler) }, [isPlaying]) const handleTimeUpdate = () => { if (!audioRef.current) return setCurrentTime(audioRef.current.currentTime) } const handleLoadedMetadata = () => { if (!audioRef.current) return setDuration(audioRef.current.duration) } const handleEnded = () => { setPlaying(false) syncProgress() } const handleSeekBar = (e: React.ChangeEvent) => { const t = parseFloat(e.target.value) if (audioRef.current) audioRef.current.currentTime = t seek(t) } const fmtTime = (s: number) => { if (!isFinite(s)) return '0:00' const h = Math.floor(s / 3600) const m = Math.floor((s % 3600) / 60) const sec = Math.floor(s % 60) return h > 0 ? `${h}:${m.toString().padStart(2, '0')}:${sec.toString().padStart(2, '0')}` : `${m}:${sec.toString().padStart(2, '0')}` } const currentChapter = [...chapters].reverse().find((c: any) => currentTime >= c.start) || chapters[0] const addBookmark = async () => { if (!item) return const label = currentChapter?.title || fmtTime(currentTime) await createBookmark(item.id, currentTime, label) } const SPEEDS = [0.75, 1, 1.25, 1.5, 1.75, 2] const SLEEP_OPTIONS = [15 * 60, 30 * 60, 45 * 60, 60 * 60] // Chapter progress markers const chapterMarkers = chapters.map((c: any) => ({ pct: duration > 0 ? (c.start / duration) * 100 : 0, })) return (