Skip to content

useAudio

React hook to manage an audio.

Add the hook via the CLI:

sh
npx @novajslabs/cli add useAudio
sh
npx @novajslabs/cli add useAudio
sh
pnpm dlx @novajslabs/cli add useAudio

Or copy and paste the code into your project:

ts
import {useEffect, useState, RefObject} from "react";

export const useAudio = (ref: RefObject<HTMLAudioElement>) => {
    const audio = ref.current;

    const [audioState, setAudioState] = useState({
        isPaused: audio ? audio?.paused : true,
        isMuted: audio ? audio?.muted : false,
        currentVolume: audio ? audio?.volume : 100,
        currentTime: audio ? audio?.currentTime : 0,
    });

    const play = () => {
        audio?.play();
        setAudioState((prev) => {
            return {
                ...prev,
                isPaused: false,
                isMuted: audio ? audio.muted : prev.isMuted,
            };
        });
    };

    const pause = () => {
        audio?.pause();
        setAudioState((prev) => {
            return {
                ...prev,
                isPaused: true,
            };
        });
    };

    const handlePlayPauseControl = (e: Event) => {
        setAudioState((prev) => {
            return {
                ...prev,
                isPaused: (e.target as HTMLAudioElement).paused,
            };
        });
    };

    const togglePause = () => (audio?.paused ? play() : pause());

    const handleVolume = (delta: number) => {
        const deltaDecimal = delta / 100;

        if (audio) {
            let newVolume = audio?.volume + deltaDecimal;

            if (newVolume >= 1) {
                newVolume = 1;
            } else if (newVolume <= 0) {
                newVolume = 0;
            }

            audio.volume = newVolume;
            setAudioState((prev) => {
                return {
                    ...prev,
                    currentVolume: newVolume * 100,
                };
            });
        }
    };

    const handleVolumeControl = (e: Event) => {
        if (e.target && audio) {
            const newVolume = (e.target as HTMLAudioElement).volume * 100;

            handleMute(audio.muted);
            setAudioState((prev) => ({
                ...prev,
                currentVolume: newVolume,
            }));
        }
    };

    const handleMute = (mute: boolean) => {
        if (audio) {
            audio.muted = mute;
            setAudioState((prev) => {
                return {
                    ...prev,
                    isMuted: mute,
                };
            });
        }
    };

    const handleTime = (delta: number = 5) => {
        if (audio) {
            let newTime = audio.currentTime + delta;

            if (newTime >= audio.duration) {
                newTime = audio.duration;
            } else if (newTime <= 0) {
                newTime = 0;
            }

            audio.currentTime = newTime;
            setAudioState((prev) => {
                return {
                    ...prev,
                    currentTime: newTime,
                };
            });
        }
    };

    const handleTimeControl = (e: Event) => {
        setAudioState((prev) => {
            return {
                ...prev,
                currentTime: (e.target as HTMLAudioElement).currentTime,
            };
        });
    };

    useEffect(() => {
        return () => {
            pause();
        };
    }, []);

    useEffect(() => {
        if (audio) {
            audio.addEventListener("volumechange", handleVolumeControl);
            audio.addEventListener("play", handlePlayPauseControl);
            audio.addEventListener("pause", handlePlayPauseControl);
            audio.addEventListener("timeupdate", handleTimeControl);

            return () => {
                audio.removeEventListener("volumechange", handleVolumeControl);
                audio.removeEventListener("play", handlePlayPauseControl);
                audio.removeEventListener("pause", handlePlayPauseControl);
                audio.removeEventListener("timeupdate", handleTimeControl);
            };
        }
    }, [audio]);

    return {
        ...audioState,
        play,
        pause,
        togglePause,
        increaseVolume: (increase: number = 5) => handleVolume(increase),
        decreaseVolume: (decrease: number = 5) => handleVolume(decrease * -1),
        mute: () => handleMute(true),
        unmute: () => handleMute(false),
        toggleMute: () => handleMute(!audio?.muted),
        forward: (increase: number = 5) => handleTime(increase),
        back: (decrease: number = 5) => handleTime(decrease * -1),
    };
};
js
import {useEffect, useState} from "react";

export const useAudio = (ref) => {
    const audio = ref.current;

    const [audioState, setAudioState] = useState({
        isPaused: audio ? audio?.paused : true,
        isMuted: audio ? audio?.muted : false,
        currentVolume: audio ? audio?.volume : 100,
        currentTime: audio ? audio?.currentTime : 0,
    });

    const play = () => {
        audio?.play();
        setAudioState((prev) => {
            return {
                ...prev,
                isPaused: false,
                isMuted: audio ? audio.muted : prev.isMuted,
            };
        });
    };

    const pause = () => {
        audio?.pause();
        setAudioState((prev) => {
            return {
                ...prev,
                isPaused: true,
            };
        });
    };

    const handlePlayPauseControl = (e) => {
        setAudioState((prev) => {
            return {
                ...prev,
                isPaused: e.target.paused,
            };
        });
    };

    const togglePause = () => (audio?.paused ? play() : pause());

    const handleVolume = (delta) => {
        const deltaDecimal = delta / 100;

        if (audio) {
            let newVolume = audio?.volume + deltaDecimal;

            if (newVolume >= 1) {
                newVolume = 1;
            } else if (newVolume <= 0) {
                newVolume = 0;
            }

            audio.volume = newVolume;
            setAudioState((prev) => {
                return {
                    ...prev,
                    currentVolume: newVolume * 100,
                };
            });
        }
    };

    const handleVolumeControl = (e) => {
        if (e.target && audio) {
            const newVolume = e.target.volume * 100;

            handleMute(audio.muted);
            setAudioState((prev) => ({
                ...prev,
                currentVolume: newVolume,
            }));
        }
    };

    const handleMute = (mute) => {
        if (audio) {
            audio.muted = mute;
            setAudioState((prev) => {
                return {
                    ...prev,
                    isMuted: mute,
                };
            });
        }
    };

    const handleTime = (delta = 5) => {
        if (audio) {
            let newTime = audio.currentTime + delta;

            if (newTime >= audio.duration) {
                newTime = audio.duration;
            } else if (newTime <= 0) {
                newTime = 0;
            }

            audio.currentTime = newTime;
            setAudioState((prev) => {
                return {
                    ...prev,
                    currentTime: newTime,
                };
            });
        }
    };

    const handleTimeControl = (e) => {
        setAudioState((prev) => {
            return {
                ...prev,
                currentTime: e.target.currentTime,
            };
        });
    };

    useEffect(() => {
        return () => {
            pause();
        };
    }, []);

    useEffect(() => {
        if (audio) {
            audio.addEventListener("volumechange", handleVolumeControl);
            audio.addEventListener("play", handlePlayPauseControl);
            audio.addEventListener("pause", handlePlayPauseControl);
            audio.addEventListener("timeupdate", handleTimeControl);

            return () => {
                audio.removeEventListener("volumechange", handleVolumeControl);
                audio.removeEventListener("play", handlePlayPauseControl);
                audio.removeEventListener("pause", handlePlayPauseControl);
                audio.removeEventListener("timeupdate", handleTimeControl);
            };
        }
    }, [audio]);

    return {
        ...audioState,
        play,
        pause,
        togglePause,
        increaseVolume: (increase = 5) => handleVolume(increase),
        decreaseVolume: (decrease = 5) => handleVolume(decrease * -1),
        mute: () => handleMute(true),
        unmute: () => handleMute(false),
        toggleMute: () => handleMute(!audio?.muted),
        forward: (increase = 5) => handleTime(increase),
        back: (decrease = 5) => handleTime(decrease * -1),
    };
};

Requirements

React 16.8 or higher

Parameters

ref required

  • Type: HTMLAudioElement

The HTML audio element to be manage.

Return values

isPaused

Type: boolean

Indicates whether the audio is currently paused (true) or playing (false).

isMuted

Type: boolean

Indicates whether the audio is currently muted (true) or unmuted (false).

currentVolume

Type: number

The current volume level of the audio, ranging from 0 to 100.

currentTime

Type: number

The current playback position (in seconds) of the audio.

play

Type: function

Play the audio.

pause

Type: function

Pause the audio.

togglePause

Type: function

Toggle between playing and pausing the audio.

increaseVolume

Type: function

Increase the volume by a specified amount.

decreaseVolume

Type: function

Decrease the volume by a specified amount.

mute

Type: function

Mute the audio.

unmute

Type: function

Unmute the audio.

toggleMute

Type: function

Toggle between muting and unmuting the audio.

forward

Type: function

Fast forward the audio by a specified number of seconds.

back

Type: function

Rewind the audio by a specified number of seconds.

Example

tsx
import {useRef} from "react";
import {useAudio} from "./hooks/useAudio";

const App = () => {
    const audioRef = useRef<HTMLAudioElement>(null);
    const {
        isPaused,
        isMuted,
        currentVolume,
        currentTime,
        play,
        pause,
        togglePause,
        increaseVolume,
        decreaseVolume,
        mute,
        unmute,
        toggleMute,
        forward,
        back,
    } = useAudio(audioRef);

    return (
        <div>
            <audio ref={audioRef} src="audio.mp3"/>
            <div>
                <button onClick={togglePause}>{isPaused ? "Play" : "Pause"}</button>
                <button onClick={toggleMute}>{isMuted ? "Unmute" : "Mute"}</button>
                <input
                    type="range"
                    min={0}
                    max={100}
                    value={currentVolume}
                    onChange={(e) => increaseVolume(Number(e.target.value))}
                />
                <input
                    type="range"
                    min={0}
                    max={videoRef.current?.duration || 0}
                    value={currentTime}
                    onChange={(e) =>
                        videoRef.current &&
                        (videoRef.current.currentTime = Number(e.target.value))
                    }
                />
                <button onClick={() => forward(10)}>Forward</button>
                <button onClick={() => back(10)}>Back</button>
            </div>
        </div>
    );
};

export default App;

Use cases

Here are some use cases where this React hook is useful:

  • Controlling audio playback in a custom media player
  • Implementing a podcast player with seek functionality
  • Building a music streaming app with volume control
  • Creating an audio preview component for an e-commerce site
  • Developing an educational platform with audio lectures