Skip to main content

Overview

AudioTimeContext is a React Context provider that manages playback position persistence and user preferences for audio time tracking. It provides a centralized way to store, retrieve, and manage playback times for podcast episodes using both React state and localStorage.
This Context works alongside the Redux audioTimeSlice to provide comprehensive playback time management. The Context handles global state and localStorage persistence, while the slice manages Redux-specific state.

Provider Setup

The AudioTimeProvider wraps the application to provide playback time management:
src/context/AudioTimeContext.jsx
export const AudioTimeProvider = ({ children }) => {
    const [playbackTimes, setPlaybackTimes] = useState({});
    const [savePlaybackTime, setSavePlaybackTime] = useState(true);
    
    // Provider implementation
    return (
        <AudioTimeContext.Provider value={{
            playbackTimes,
            updatePlaybackTime,
            getPlaybackTime,
            savePlaybackTime,
            toggleSavePlaybackTime,
            clearPlaybackTimes
        }}>
            {children}
        </AudioTimeContext.Provider>
    );
};

Hook: useAudioTime

The useAudioTime hook provides access to the audio time context:
import { useAudioTime } from '../context/AudioTimeContext';

function MyComponent() {
    const {
        playbackTimes,
        updatePlaybackTime,
        getPlaybackTime,
        savePlaybackTime,
        toggleSavePlaybackTime,
        clearPlaybackTimes
    } = useAudioTime();
}
The hook must be used within an AudioTimeProvider. It will throw an error if used outside the provider:
"useAudioTime must be used within an AudioTimeProvider"

Context Values

playbackTimes

playbackTimes
object
Object mapping episode titles to their current playback positions in secondsStructure: { [episodeTitle: string]: number }Example:
{
  "Episode 123: Title Here": 345.67,
  "Episode 124: Another Episode": 120.5
}

updatePlaybackTime

updatePlaybackTime
function
Updates the playback time for a specific episodeSignature: (audioTitle: string, currentTime: number) => voidParameters:
  • audioTitle - The title of the podcast episode
  • currentTime - Current playback position in seconds
Behavior:
  • Only updates if savePlaybackTime is true
  • Persists to localStorage under key nsnPlaybackTimes
  • Merges with existing playback times
src/context/AudioTimeContext.jsx
const updatePlaybackTime = useCallback(
    (audioTitle, currentTime) => {
        if (!savePlaybackTime) return;

        const newTimes = {
            ...playbackTimes,
            [audioTitle]: currentTime
        };
        setPlaybackTimes(newTimes);
        localStorage.setItem("nsnPlaybackTimes", JSON.stringify(newTimes));
    },
    [playbackTimes, savePlaybackTime]
);

getPlaybackTime

getPlaybackTime
function
Retrieves the saved playback time for a specific episodeSignature: (audioTitle: string) => numberReturns: Playback position in seconds, or 0 if not found or saving is disabled
src/context/AudioTimeContext.jsx
const getPlaybackTime = (audioTitle) => {
    return savePlaybackTime ? playbackTimes[audioTitle] || 0 : 0;
};

savePlaybackTime

savePlaybackTime
boolean
User preference flag indicating whether playback times should be savedDefault: truelocalStorage key: nsnSavePlaybackTime

toggleSavePlaybackTime

toggleSavePlaybackTime
function
Toggles the playback time saving preferenceSignature: () => voidBehavior:
  • Flips the savePlaybackTime boolean
  • Persists preference to localStorage
  • If disabling, clears all stored playback times
src/context/AudioTimeContext.jsx
const toggleSavePlaybackTime = () => {
    const newValue = !savePlaybackTime;
    setSavePlaybackTime(newValue);
    localStorage.setItem("nsnSavePlaybackTime", JSON.stringify(newValue));

    if (!newValue) {
        setPlaybackTimes({});
        localStorage.removeItem("nsnPlaybackTimes");
    }
};

clearPlaybackTimes

clearPlaybackTimes
function
Clears all stored playback timesSignature: () => voidBehavior:
  • Removes all playback times from state and localStorage
  • Shows success/error toast notification
  • Used by Settings component for manual cleanup
src/context/AudioTimeContext.jsx
const clearPlaybackTimes = () => {
    try {
        setPlaybackTimes({});
        localStorage.removeItem("nsnPlaybackTimes");
        toast.success("Tiempos Borrados", {
            duration: 1000,
            position: isMobile ? "bottom-center" : "bottom-left"
        });
    } catch (error) {
        toast.error("Error al borrar los tiempos", {
            duration: 1000,
            position: isMobile ? "bottom-center" : "bottom-left"
        });
    }
};

localStorage Integration

The context uses two localStorage keys:
KeyTypePurpose
nsnPlaybackTimesJSON objectStores all playback positions
nsnSavePlaybackTimeJSON booleanStores user preference for saving times

Initialization

On mount, the provider loads saved data from localStorage:
src/context/AudioTimeContext.jsx
useEffect(() => {
    const savedTimes = localStorage.getItem("nsnPlaybackTimes");
    const savedPreference = localStorage.getItem("nsnSavePlaybackTime");

    if (savedTimes) {
        setPlaybackTimes(JSON.parse(savedTimes));
    }

    if (savedPreference !== null) {
        setSavePlaybackTime(JSON.parse(savedPreference));
    }
}, []);

Usage Examples

Saving Playback Position

import { useAudioTime } from '../context/AudioTimeContext';

function AudioPlayer({ episodeTitle }) {
    const { updatePlaybackTime } = useAudioTime();
    const audioRef = useRef();
    
    const handleTimeUpdate = () => {
        const currentTime = audioRef.current.currentTime;
        updatePlaybackTime(episodeTitle, currentTime);
    };
    
    return (
        <audio
            ref={audioRef}
            onTimeUpdate={handleTimeUpdate}
            src={audioSrc}
        />
    );
}

Resuming Playback

import { useAudioTime } from '../context/AudioTimeContext';

function AudioPlayer({ episodeTitle }) {
    const { getPlaybackTime } = useAudioTime();
    const audioRef = useRef();
    
    useEffect(() => {
        const savedTime = getPlaybackTime(episodeTitle);
        if (savedTime > 0 && audioRef.current) {
            audioRef.current.currentTime = savedTime;
        }
    }, [episodeTitle, getPlaybackTime]);
    
    return <audio ref={audioRef} src={audioSrc} />;
}

Settings Toggle

import { useAudioTime } from '../context/AudioTimeContext';

function SettingsPanel() {
    const { savePlaybackTime, toggleSavePlaybackTime } = useAudioTime();
    
    return (
        <div>
            <label>
                <input
                    type="checkbox"
                    checked={savePlaybackTime}
                    onChange={toggleSavePlaybackTime}
                />
                Guardar tiempo de reproducción
            </label>
        </div>
    );
}

Integration with Redux

The context complements the Redux audioTimeSlice:
  • Context: Provides React-specific state management and localStorage operations
  • Redux Slice: Manages playback times within the Redux store for global state consistency
Both systems can be used together or independently depending on component needs.

Mobile Detection

The context integrates with useMobileDetect to position toast notifications appropriately:
src/context/AudioTimeContext.jsx
const isMobile = useMobileDetect();

toast.success("Tiempos Borrados", {
    position: isMobile ? "bottom-center" : "bottom-left"
});

Best Practices

1

Wrap your app

Place AudioTimeProvider at the root of your component tree
<AudioTimeProvider>
    <App />
</AudioTimeProvider>
2

Use the hook

Access context values via the useAudioTime hook
3

Handle errors

The provider includes error handling for localStorage operations
4

Respect user preferences

Always check savePlaybackTime before updating times
The AudioTimeContext pattern provides a lightweight alternative to Redux for managing playback state, especially useful for components that need frequent updates without triggering Redux re-renders.

Build docs developers (and LLMs) love