feat: implement backend episode management and progress tracking controllers and routes

This commit is contained in:
Michael committed 2026-04-16 14:48:32 +02:00
1 parent 686c4d9007
commit aa54304bf3
9 files changed
+325 -30

No files matched your search

+28 -1
View File
@@ -24,12 +24,27 @@ export default function AudioPlayer({ episode, onClose, userId }: AudioPlayerPro
const [isPlaying, setIsPlaying] = useState(false);
const [currentTime, setCurrentTime] = useState(0);
const [duration, setDuration] = useState(0);
const [volume, setVolume] = useState(1);
const [volume, setVolume] = useState(() => parseFloat(localStorage.getItem('podcastic-volume') || '1'));
const [playbackSpeed, setPlaybackSpeed] = useState(() => parseFloat(localStorage.getItem('podcastic-speed') || '1'));
const [isMuted, setIsMuted] = useState(false);
const [isExpanded, setIsExpanded] = useState(false);
const podcast = typeof episode?.podcastId === 'object' ? episode.podcastId : null;
// Sync settings to audio element
useEffect(() => {
if (audioRef.current) {
audioRef.current.volume = isMuted ? 0 : volume;
audioRef.current.playbackRate = playbackSpeed;
}
}, [volume, isMuted, playbackSpeed, episode]);
// Save settings
useEffect(() => {
localStorage.setItem('podcastic-volume', volume.toString());
localStorage.setItem('podcastic-speed', playbackSpeed.toString());
}, [volume, playbackSpeed]);
// Save progress periodically
useEffect(() => {
if (!episode || !userId) return;
@@ -60,6 +75,12 @@ export default function AudioPlayer({ episode, onClose, userId }: AudioPlayerPro
setIsPlaying(!isPlaying);
};
const changeSpeed = () => {
const speeds = [1, 1.25, 1.5, 2, 0.75];
const nextIndex = (speeds.indexOf(playbackSpeed) + 1) % speeds.length;
setPlaybackSpeed(speeds[nextIndex]);
};
const handleSeek = (delta: number) => {
if (!audioRef.current) return;
audioRef.current.currentTime = Math.max(0, Math.min(duration, currentTime + delta));
@@ -163,6 +184,9 @@ export default function AudioPlayer({ episode, onClose, userId }: AudioPlayerPro
{/* Right actions */}
<div className="flex items-center gap-2 shrink-0">
<button onClick={changeSpeed} className="px-2 py-1 rounded-lg bg-white/5 text-[10px] font-black text-slate-500 hover:text-white transition-all uppercase tracking-tighter w-10">
{playbackSpeed}x
</button>
<button onClick={toggleMute} className="p-2 text-slate-500 hover:text-white transition-colors">
{isMuted ? <VolumeX className="w-4 h-4" /> : <Volume2 className="w-4 h-4" />}
</button>
@@ -249,6 +273,9 @@ export default function AudioPlayer({ episode, onClose, userId }: AudioPlayerPro
{/* Volume */}
<div className="flex items-center gap-4">
<button onClick={changeSpeed} className="px-3 py-1.5 rounded-xl bg-white/5 text-xs font-black text-slate-400 hover:text-white transition-all uppercase tracking-widest">
{playbackSpeed}x Speed
</button>
<button onClick={toggleMute} className="text-slate-400 hover:text-white transition-colors shrink-0">
{isMuted ? <VolumeX className="w-5 h-5" /> : <Volume2 className="w-5 h-5" />}
</button>
+10
View File
@@ -66,6 +66,16 @@ export default function EpisodeCard({ episode, onPlay }: EpisodeCardProps) {
</div>
</div>
</div>
{/* Local Progress Bar */}
{episode.progress && !episode.progress.isCompleted && (
<div className="absolute bottom-0 left-0 right-0 h-1 bg-white/5 rounded-b-[2rem] overflow-hidden">
<div
className="h-full bg-accent-indigo shadow-glow-indigo transition-all duration-1000"
style={{ width: `${Math.min(100, (episode.progress.position / episode.duration) * 100)}%` }}
/>
</div>
)}
</div>
);
}