DEV Community

Nilesh Kumar
Nilesh Kumar

Posted on

Building a Custom Audio Player in React: A Step-by-Step Guide

Introduction: In this blog, we’re building a simple yet powerful audio player component in React. With the ability to play, pause, stop, and skip audio, this component leverages React hooks and interactive icons to create a seamless audio experience. Let's dive into each part and learn how to integrate this audio player in any project that uses audio in base64 format.

custom audio player

Component Breakdown

This PlayStory component makes use of React Icons for play, pause, skip, and stop buttons. It also handles audio state and playback using useState and useEffect hooks.

import {
  IoPlayCircleOutline,
  IoPauseCircleOutline,
  IoStopCircleOutline,
  IoPlaySkipBackCircleOutline,
  IoPlaySkipForwardCircleOutline,
} from "react-icons/io5";
Enter fullscreen mode Exit fullscreen mode

These imported icons give the player a clean, accessible design. Let's go over each major part:

Install react-icon from npm store

npm i react-icons
Enter fullscreen mode Exit fullscreen mode

1. Component Initialization

The PlayStory component receives an audio prop in base64 format. This is converted into a playable audio file inside the useEffect hook. The playing state determines whether the audio is currently playing or paused, while duration and currentTime handle the audio’s total time and current time display.

Key states:

  • playing: Boolean, toggles audio play and pause.
  • audioFile: Holds the created audio file object.
  • duration: Stores formatted total duration of the audio.
  • currentTime: Stores formatted time of the current playback position.

2. Formatting Audio Time

The formatTime function converts raw audio seconds into an "MM
" format. This is used to show duration and currentTime for a better user experience.

const formatTime = (seconds) => {
  const minutes = Math.floor(seconds / 60);
  const remainingSeconds = Math.floor(seconds % 60);
  return `${minutes.toString().padStart(2, "0")}:${remainingSeconds.toString().padStart(2, "0")}`;
};
Enter fullscreen mode Exit fullscreen mode

3. Audio Controls

This component comes with essential playback controls:

  • Play/Pause Toggle
    The handlePlayPause function toggles between playing and pausing the audio based on the playing state. When paused, it stops audio playback; when played, it resumes.

  • Stop
    The handleStop function stops the audio, sets currentTime to the beginning, and resets the playing state.

  • Skip Back/Forward
    The handleSkipBack and handleSkipForward functions allow users to jump back or forward by 10 seconds in the audio.

const handlePlayPause = () => {
  if (audioFile) {
    if (playing) {
      audioFile.pause();
    } else {
      audioFile.play();
    }
    isPlaying((pre) => !pre);
  }
};

const handleStop = () => {
    audioFile.pause();
    audioFile.currentTime = 0;
    isPlaying(false);
  };

  const handleSkipBack = () => {
    const newTime = audioFile.currentTime - 10;
    audioFile.currentTime = newTime < 0 ? 0 : newTime;
  };

  const handleSkipForward = () => {
    const newTime = audioFile.currentTime + 10;
    audioFile.currentTime =
      newTime > audioFile.duration ? audioFile.duration : newTime;
  };
Enter fullscreen mode Exit fullscreen mode

4. State to be manage for player

 const { base64audio } = props; // get the audio data in base64
  const [playing, isPlaying] = useState(false);
  const [audioFile, setAudioFile] = useState(null);
  const [duration, setDuration] = useState("00:00");
  const [currentTime, setCurrentTime] = useState("00:00");
Enter fullscreen mode Exit fullscreen mode

5. Managing Audio Playback with Hooks

Two useEffect hooks are used for:

  • Setting up the audio file from the base64 prop, and setting duration when audio is loaded.
  • Updating currentTime while the audio plays, and resetting playing state when the audio ends.
useEffect(() => {
  if (base64adio) {
    const audio = new Audio(`data:audio/x-wav;base64, ${base64adio}`);
    setAudioFile(audio);

    audio.onloadeddata = () => {
      const audioDuration = audio.duration;
      if (typeof audioDuration === "number" && !isNaN(audioDuration)) {
        setDuration(formatTime(audioDuration));
      }
    };
  }
}, [base64adio]);
Enter fullscreen mode Exit fullscreen mode

6. Update current time while audio is playing

adding the event to manage current time

useEffect(() => {
    if (audioFile) {
      // // Update current time while audio is playing
      audioFile.ontimeupdate = () => {
        const duration = audioFile.duration;
        const currentTime = audioFile.currentTime;
        if (duration === currentTime) {
          isPlaying(false);
          audioFile.currentTime = 0;
          audioFile.pause();
          setCurrentTime(formatTime(audioFile.currentTime));
        } else {
          setCurrentTime(formatTime(currentTime));
        }
      };
    }
Enter fullscreen mode Exit fullscreen mode

7. Rendering the Component

The component’s return function includes:

  • currentTime and duration display.
  • Icon buttons for skip back, play/pause, stop, and skip forward. Each icon is styled and sized for a better look and user interaction.
return (
  <div className="flex items-center justify-between mx-1">
    {currentTime}
    <IoPlaySkipBackCircleOutline onClick={handleSkipBack} size={25} />
    {playing ? (
      <IoPauseCircleOutline onClick={handlePlayPause} size={25} />
    ) : (
      <IoPlayCircleOutline onClick={handlePlayPause} size={25} />
    )}
    <IoStopCircleOutline onClick={handleStop} size={25} />
    <IoPlaySkipForwardCircleOutline onClick={handleSkipForward} size={25} />
    {duration}
  </div>
);
Enter fullscreen mode Exit fullscreen mode

Usage Example

To integrate this audio player in your project, you only need to pass the audio data in base64 format to the any component. The player will handle playback controls seamlessly, providing an easy-to-use audio experience.

Complete code

Here is the complete working code.


import React, { useEffect, useState } from "react";
import {
  IoPlayCircleOutline,
  IoPauseCircleOutline,
  IoStopCircleOutline,
  IoPlaySkipBackCircleOutline,
  IoPlaySkipForwardCircleOutline,
} from "react-icons/io5";

const PlayStory = (props) => {
  const { base64adio } = props;
  const [playing, isPlaying] = useState(false);
  const [audioFile, setAudioFile] = useState(null);
  const [duration, setDuration] = useState("00:00");
  const [currentTime, setCurrentTime] = useState("00:00");

  const handleStop = () => {
    audioFile.pause();
    audioFile.currentTime = 0;
    isPlaying(false);
  };

  const handleSkipBack = () => {
    const newTime = audioFile.currentTime - 10;
    audioFile.currentTime = newTime < 0 ? 0 : newTime;
  };

  const handleSkipForward = () => {
    const newTime = audioFile.currentTime + 10;
    audioFile.currentTime =
      newTime > audioFile.duration ? audioFile.duration : newTime;
  };

  const handlePlayPause = () => {
    if (audioFile) {
      if (playing) {
        audioFile.pause();
      } else {
        audioFile.play();
      }
      isPlaying((pre) => !pre);
    }
  };

  useEffect(() => {
    if (base64adio) {
      const audio = new Audio(`data:audio/x-wav;base64, ${base64adio}`);
      setAudioFile(audio);

      audio.onloadeddata = () => {
        const audioDuration = audio.duration;
        if (typeof audioDuration === "number" && !isNaN(audioDuration)) {
          setDuration(formatTime(audioDuration));
        }
      };
    }
  }, [base64adio]);

  useEffect(() => {
    if (audioFile) {
      // // Update current time while audio is playing
      audioFile.ontimeupdate = () => {
        const duration = audioFile.duration;
        const currentTime = audioFile.currentTime;
        if (duration === currentTime) {
          isPlaying(false);
          audioFile.currentTime = 0;
          audioFile.pause();
          setCurrentTime(formatTime(audioFile.currentTime));
        } else {
          setCurrentTime(formatTime(currentTime));
        }
      };
    }

    return () => {
      if (audioFile) {
        audioFile.onloadeddata = null; // Cleanup on unmount
        audioFile.ontimeupdate = null; // Cleanup on unmount
      }
    };
  }, [audioFile]);

  return (
    <div className="flex items-center justify-between mx-1">
      {currentTime}
      <IoPlaySkipBackCircleOutline
        className="cursor-pointer text-gray-800 size-6"
        size={25}
        onClick={handleSkipBack}
      />
      {playing ? (
        <IoPauseCircleOutline
          onClick={handlePlayPause}
          className="cursor-pointer text-gray-800 size-6"
          size={25}
        />
      ) : (
        <IoPlayCircleOutline
          onClick={handlePlayPause}
          className="cursor-pointer text-gray-800 size-6"
          size={25}
        />
      )}
      <IoStopCircleOutline
        className="cursor-pointer text-gray-800 size-6"
        size={25}
        onClick={() => handleStop()}
      />
      <IoPlaySkipForwardCircleOutline
        className="cursor-pointer text-gray-800 size-6"
        size={25}
        onClick={handleSkipForward}
      />
      {duration}
    </div>
  );
};

export default PlayStory;

const formatTime = (seconds) => {
  const minutes = Math.floor(seconds / 60);
  const remainingSeconds = Math.floor(seconds % 60);
  return `${minutes.toString().padStart(2, "0")}:${remainingSeconds
    .toString()
    .padStart(2, "0")}`;
};
Enter fullscreen mode Exit fullscreen mode

Conclusion

This audio player component provides a great way to integrate audio playback controls in React applications. With customizable controls and a clear visual layout, it’s easy to add, modify, or expand its functionalities.

Top comments (0)