Overview
The Nadie Sabe Nada podcast application features a robust audio player system with persistent playback, automatic progress tracking, and advanced controls. The player is built using React’s react-h5-audio-player library and integrates seamlessly with Redux state management.
Player Components
The audio player system consists of two main components:
Persistent Player
The persistent player appears as a slide-in panel that remains accessible across navigation. It provides full audio controls and information about the currently playing episode.
The persistent player automatically saves your playback position every second, so you can resume listening exactly where you left off.
Key Features
Automatic Resume
When you start playing an episode, the player automatically resumes from your last saved position
Volume Persistence
Volume settings are saved to localStorage and restored on your next visit
Playback Tracking
Progress is updated every second and synchronized with Redux state
Implementation Details
Playback Time Update
The player uses a throttled time update mechanism to avoid excessive state updates:
const handleTimeUpdate = (e) => {
const currentTime = e.target.currentTime;
const now = Date.now();
if (now - lastTimeUpdateRef.current >= 1000) {
dispatch(updatePlaybackTime({ title: currentPodcast.title, time: currentTime }));
lastTimeUpdateRef.current = now;
}
};
Playback position is only updated once per second to optimize performance and reduce Redux dispatches.
Volume Management
Volume settings persist across sessions using localStorage:
const [volume, setVolume] = useState(() => {
const savedVolume = localStorage.getItem("nsnPlayerVolume");
return savedVolume ? parseFloat(savedVolume) : 1;
});
const handleVolumeChange = (e) => {
const newVolume = e.target.volume;
setVolume(newVolume);
localStorage.setItem("nsnPlayerVolume", newVolume.toString());
};
Auto-Completion Detection
When an episode finishes playing, it’s automatically marked as completed:
const handleEnded = () => {
dispatch(updatePlaybackTime({ title: currentPodcast.title, time: 0 }));
dispatch(markAsCompleted(currentPodcast.title));
};
Player Controls
The audio player includes the following controls:
- Play/Pause - Start or pause playback
- Jump Controls - Skip forward or backward by set intervals
- Volume Control - Adjust audio volume with persistent settings
- Progress Bar - Seek to any point in the episode
- Loop Control - Enable repeat playback
- Sleep Timer - Schedule automatic playback stop (see Sleep Timer section below)
Sleep Timer
The sleep timer allows you to automatically stop playback after a specified duration.
Available Time Options
- 5 minutes
- 15 minutes (default)
- 30 minutes
- 45 minutes
- 60 minutes (1 hour)
Using the Sleep Timer
Start Playback
Begin playing an episode first - the timer only works during active playback
Select Duration
Choose your desired duration from the dropdown menu
Activate Timer
Click the timer button to start the countdown
Monitor Progress
The timer displays remaining time in MM:SS format
The sleep timer will only activate if playback is in progress. Starting the timer while paused will show a warning message.
Timer Behavior
const handleTimerStart = () => {
if (!isPlaying) {
toast.custom(
<div className={styles.confirmToast}>
<div className={styles.confirmHeader}>
<Warning className={styles.warningIcon} />
<h3>¡Atención!</h3>
</div>
<p className={styles.confirmMessage}>
Inicia la reproducción primero para activar el temporizador.
</p>
</div>,
{ duration: 3000, position: isMobile ? "bottom-center" : "bottom-left" }
);
return;
}
setIsTimerActive(true);
setTimeLeft(selectedTime * 60);
};
Auto-Cancellation
The sleep timer automatically cancels when:
- An episode is marked as completed
- The timer countdown reaches zero
- You manually cancel it using the cancel button
useEffect(() => {
if (currentPodcast && completedEpisodes.includes(currentPodcast.title) && isTimerActive) {
handleTimerCancel(true);
}
}, [completedEpisodes, currentPodcast, isTimerActive, handleTimerCancel]);
MP3 Player Cards
Each episode card on the main page includes quick audio controls:
Card Controls
- Play/Pause Button - Start or pause the episode
- Download Button - Download the episode for offline listening
- Complete Button - Mark episode as completed
The play button icon changes based on playback state, showing a pause icon when the episode is currently playing.
Visual Indicators
Cards display visual status indicators:
- Headphones Icon - Episode has been started (shows playback time on hover)
- Checkmark Icon - Episode has been completed
- Green Highlight - Episode is currently playing
State Management
The player uses Redux for state management with multiple slices:
Player Slice
Manages current playback state:
const handlePlay = () => {
if (completedEpisodes.includes(currentPodcast.title)) {
dispatch(removeFromCompleted(currentPodcast.title));
}
dispatch(togglePlay(true));
};
Audio Time Slice
Tracks playback positions for all episodes:
const { playbackTimes, savePlaybackTime } = useSelector((state) => state.audioTime);
const savedTime = savePlaybackTime ? playbackTimes[currentPodcast.title] || 0 : 0;
const audio = audioRef.current.audio.current;
audio.currentTime = savedTime;
Playback positions are stored per episode title, allowing you to have multiple episodes in progress simultaneously.
Best Practices
For optimal experience:
- Allow the browser to access local storage for progress tracking
- Use the sleep timer when listening before bed
- Enable the “Save Playback Time” setting in Settings
- Regularly mark completed episodes to keep your library organized
Animations
The persistent player features smooth animations powered by Framer Motion:
<motion.div
className={styles.persistentPlayer}
initial={{ opacity: 0, x: windowWidth }}
animate={{ opacity: 1, x: 0 }}
exit={{ opacity: 0, x: windowWidth - 100, transition: { duration: 0.9 } }}
transition={{ duration: 0.9, type: "spring", stiffness: 120, damping: 10 }}
>
The player slides in from the right side of the screen with a spring animation for a smooth, natural feel.