import { useKeyboard } from "@opentui/solid" import { PlaybackControls } from "./PlaybackControls" import { MergedWaveform } from "./MergedWaveform" import { useAudio } from "../hooks/useAudio" import type { Episode } from "../types/episode" type PlayerProps = { focused: boolean episode?: Episode | null onExit?: () => void } const SAMPLE_EPISODE: Episode = { id: "sample-ep", podcastId: "sample-podcast", title: "A Tour of the Productive Mind", description: "A short guided session on building creative focus.", audioUrl: "", duration: 2780, pubDate: new Date(), } export function Player(props: PlayerProps) { const audio = useAudio() // The episode to display — prefer a passed-in episode, then the // currently-playing episode, then fall back to the sample. const episode = () => props.episode ?? audio.currentEpisode() ?? SAMPLE_EPISODE const dur = () => audio.duration() || episode().duration || 1 useKeyboard((key: { name: string }) => { if (!props.focused) return if (key.name === "space") { if (audio.currentEpisode()) { audio.togglePlayback() } else { // Nothing loaded yet — start playing the displayed episode const ep = episode() if (ep.audioUrl) { audio.play(ep) } } return } if (key.name === "escape") { props.onExit?.() return } if (key.name === "left") { audio.seekRelative(-10) } if (key.name === "right") { audio.seekRelative(10) } if (key.name === "up") { audio.setVolume(Math.min(1, Number((audio.volume() + 0.05).toFixed(2)))) } if (key.name === "down") { audio.setVolume(Math.max(0, Number((audio.volume() - 0.05).toFixed(2)))) } if (key.name === "s") { const next = audio.speed() >= 2 ? 0.5 : Number((audio.speed() + 0.25).toFixed(2)) audio.setSpeed(next) } }) const progressPercent = () => { const d = dur() if (d <= 0) return 0 return Math.min(100, Math.round((audio.position() / d) * 100)) } const formatTime = (seconds: number) => { const m = Math.floor(seconds / 60) const s = Math.floor(seconds % 60) return `${m}:${String(s).padStart(2, "0")}` } return ( Now Playing {formatTime(audio.position())} / {formatTime(dur())} ({progressPercent()}%) {audio.error() && ( {audio.error()} )} {episode().title} {episode().description} audio.seek(next)} /> { if (audio.currentEpisode()) { audio.togglePlayback() } else { const ep = episode() if (ep.audioUrl) audio.play(ep) } }} onPrev={() => audio.seek(0)} onNext={() => audio.seek(dur())} onSpeedChange={(s: number) => audio.setSpeed(s)} onVolumeChange={(v: number) => audio.setVolume(v)} /> Space play/pause | Left/Right seek 10s | Up/Down volume | S speed | Esc back ) }