Overview
The audio time slice manages playback position tracking for each podcast episode, allowing users to resume from where they left off.
Key Features:
Persists playback times to localStorage
Optional save/disable functionality
Individual episode time management
Bulk clear operations
State Shape
{
playbackTimes : {}, // Object mapping episode titles to times (in seconds)
savePlaybackTime : true // Whether to save playback times
}
State Structure Example
{
playbackTimes : {
"Episode 1 Title" : 1234.5 ,
"Episode 2 Title" : 567.8 ,
"Episode 3 Title" : 2890.2
},
savePlaybackTime : true
}
Initial State
State is loaded from localStorage on initialization:
src/store/slices/audioTimeSlice.js
initialState : {
playbackTimes : JSON . parse (
localStorage . getItem ( "nsnPlaybackTimes" ) || "{}"
),
savePlaybackTime : JSON . parse (
localStorage . getItem ( "nsnSavePlaybackTime" ) ?? "true"
)
}
Actions
updatePlaybackTime
Updates the playback time for a specific episode.
The episode title (used as key)
Current playback position in seconds
dispatch ( updatePlaybackTime ({
title: "Episode Title" ,
time: 1234.5
}));
Reducer Logic:
src/store/slices/audioTimeSlice.js
updatePlaybackTime : ( state , action ) => {
if ( ! state . savePlaybackTime ) return ;
const { title , time } = action . payload ;
state . playbackTimes [ title ] = time ;
localStorage . setItem (
"nsnPlaybackTimes" ,
JSON . stringify ( state . playbackTimes )
);
}
If savePlaybackTime is false, this action does nothing.
Real Usage in PersistentPlayer:
src/components/PersistentPlayer/PersistentPlayer.jsx
const handleTimeUpdate = ( e ) => {
const currentTime = e . target . currentTime ;
const now = Date . now ();
// Throttle updates to once per second
if ( now - lastTimeUpdateRef . current >= 1000 ) {
dispatch ( updatePlaybackTime ({
title: currentPodcast . title ,
time: currentTime
}));
lastTimeUpdateRef . current = now ;
}
};
< AudioPlayer
onListen = { handleTimeUpdate }
// ...
/>
The app throttles time updates to once per second to avoid excessive localStorage writes.
toggleSavePlaybackTime
Toggles whether playback times should be saved.
dispatch ( toggleSavePlaybackTime ());
Reducer Logic:
src/store/slices/audioTimeSlice.js
toggleSavePlaybackTime : ( state ) => {
state . savePlaybackTime = ! state . savePlaybackTime ;
localStorage . setItem (
"nsnSavePlaybackTime" ,
JSON . stringify ( state . savePlaybackTime )
);
// Clear all times when disabling
if ( ! state . savePlaybackTime ) {
state . playbackTimes = {};
localStorage . removeItem ( "nsnPlaybackTimes" );
}
}
Disabling this feature clears all saved playback times .
Usage in Settings:
src/components/Settings/Settings.jsx
import { toggleSavePlaybackTime } from '../../store/slices/audioTimeSlice' ;
const Settings = () => {
const { savePlaybackTime } = useSelector (( state ) => state . audioTime );
return (
< IOSSwitch
checked = { savePlaybackTime }
onChange = { () => dispatch ( toggleSavePlaybackTime ()) }
/>
);
};
clearPlaybackTimes
Clears all saved playback times.
dispatch ( clearPlaybackTimes ());
Reducer Logic:
src/store/slices/audioTimeSlice.js
clearPlaybackTimes : ( state ) => {
state . playbackTimes = {};
localStorage . removeItem ( "nsnPlaybackTimes" );
}
removePlaybackTime
Removes the saved playback time for a specific episode.
The episode title whose playback time should be removed
dispatch ( removePlaybackTime ( "Episode Title" ));
Reducer Logic:
src/store/slices/audioTimeSlice.js
removePlaybackTime : ( state , action ) => {
const episodeId = action . payload ;
delete state . playbackTimes [ episodeId ];
localStorage . setItem (
"nsnPlaybackTimes" ,
JSON . stringify ( state . playbackTimes )
);
}
Usage Example:
src/components/PodcastDetail/PodcastDetail.jsx
const handleRemoveStarted = ( song ) => {
showConfirmToast (
"¿Estás seguro de que quieres eliminar el tiempo de reproducción?" ,
() => {
dispatch ( deleteEpisode ( song . title ));
dispatch ( removePlaybackTime ( song . title ));
toast . success ( "Tiempo de reproducción eliminado" );
}
);
};
Selectors
Access audio time state in components:
import { useSelector } from 'react-redux' ;
const MyComponent = () => {
const { playbackTimes , savePlaybackTime } = useSelector (
( state ) => state . audioTime
);
// Get playback time for specific episode
const episodeTime = playbackTimes [ episodeTitle ] || 0 ;
};
Real Examples:
PodcastDetail
PersistentPlayer
Settings
src/components/PodcastDetail/PodcastDetail.jsx
const { playbackTimes } = useSelector (( state ) => state . audioTime );
const playbackTime = playbackTimes [ podcast . title ] || 0 ;
const isListened =
listenedEpisodes . includes ( podcast . title ) ||
( playbackTimes [ podcast . title ] && playbackTimes [ podcast . title ] > 0 );
src/components/PersistentPlayer/PersistentPlayer.jsx
const { playbackTimes , savePlaybackTime } = useSelector (
( state ) => state . audioTime
);
useEffect (() => {
if ( audioRef . current && currentPodcast ) {
// Resume from saved time
const savedTime = savePlaybackTime
? playbackTimes [ currentPodcast . title ] || 0
: 0 ;
audioRef . current . audio . current . currentTime = savedTime ;
}
}, [ currentPodcast ]);
src/components/Settings/Settings.jsx
const { savePlaybackTime } = useSelector (
( state ) => state . audioTime
);
Playback Time Persistence
When Times Are Saved
During Playback
Time is updated every second during playback via onListen event
On Pause
Current position is saved when user pauses
On Episode Completion
Time is reset to 0 when episode ends: const handleEnded = () => {
dispatch ( updatePlaybackTime ({
title: currentPodcast . title ,
time: 0
}));
dispatch ( markAsCompleted ( currentPodcast . title ));
};
When Times Are Cleared
User manually removes time for an episode
User clears all playback times
User disables the save feature
Episode is marked as completed
LocalStorage Keys
JSON object mapping episode titles to playback positions (in seconds) {
"Episode 1" : 1234.5 ,
"Episode 2" : 567.8
}
User preference for saving playback times. Defaults to true.
Utility Functions
Format playback times for display:
src/components/PodcastDetail/PodcastDetail.jsx
const formatTime = ( seconds ) => {
if ( ! seconds ) return "0:00" ;
const minutes = Math . floor ( seconds / 60 );
const remainingSeconds = Math . floor ( seconds % 60 );
return ` ${ minutes } : ${ remainingSeconds . toString (). padStart ( 2 , "0" ) } ` ;
};
// Usage
const playbackTime = playbackTimes [ podcast . title ] || 0 ;
const formattedTime = formatTime ( playbackTime ); // "20:35"
Complete Action Reference
import {
updatePlaybackTime , // Update time for an episode
toggleSavePlaybackTime , // Toggle save feature
clearPlaybackTimes , // Clear all times
removePlaybackTime // Remove time for one episode
} from './store/slices/audioTimeSlice' ;
Time updates are throttled to once per second to prevent excessive localStorage writes: const lastTimeUpdateRef = useRef ( 0 );
const now = Date . now ();
if ( now - lastTimeUpdateRef . current >= 1000 ) {
dispatch ( updatePlaybackTime ({ title , time }));
lastTimeUpdateRef . current = now ;
}
Times are only saved when savePlaybackTime is true, allowing users to opt out.
Only the episode title and time are stored, minimizing localStorage usage.