Cover image for How to Build a Custom Video Player in React

How to Build a Custom Video Player in React

Suraj Vishwakarma

08 Apr, 2025


Introduction

React has consistently remained the most popular JavaScript library for building modern frontends. If you're a web developer, I highly recommend learning React. And there's no better way to learn than by building real-world projects.

In one of my previous articles, I covered Building a Music Player in React. This time, let’s build something just as cool—a custom video player using React.

By the end of this tutorial, you’ll have a fully functional custom video player with:

Here’s a preview of what we’ll build:

Video Player

Let’s dive in!


Prerequisites and Environment Setup

Before we begin, make sure you're familiar with the following:

You’ll also need Node.js installed on your system.

Navigate to the folder where you want to create the project and run the following command:

npx create-react-app video-player

Once the app is created, clean up the boilerplate code to start fresh.

Dependencies

We'll use just one library:

Install it using:

npm install react-icons

Playing a Video with the Default Player

Let’s first add a video to the project and play it using the native HTML5 <video> tag. In your App.js:

import video from "./assets/video.mp4";

return (
  <video controls width="70%" className="videoPlayer" src={video}></video>
);

This gives us a basic player with default browser controls.


Building a Custom Video Player with Play/Pause Controls

Let’s build custom controls by removing the controls attribute from the <video> tag.

Here’s how the video component should look:

<video
  className="videoPlayer"
  width="70%"
  ref={videoRef}
  src={video}
></video>

Now let’s add icons using react-icons:

<div className="controls">
  <IconContext.Provider value={{ color: "white", size: "2em" }}>
    <BiSkipPrevious />
  </IconContext.Provider>
  {isPlaying ? (
    <button className="controlButton" onClick={handlePlay}>
      <IconContext.Provider value={{ color: "white", size: "2em" }}>
        <BiPause />
      </IconContext.Provider>
    </button>
  ) : (
    <button className="controlButton" onClick={handlePlay}>
      <IconContext.Provider value={{ color: "white", size: "2em" }}>
        <BiPlay />
      </IconContext.Provider>
    </button>
  )}
  <IconContext.Provider value={{ color: "white", size: "2em" }}>
    <BiSkipNext />
  </IconContext.Provider>
</div>

Here’s the state and refs setup:

import { useEffect, useRef, useState } from "react";
import { IconContext } from "react-icons";
import { BiPlay, BiPause, BiSkipNext, BiSkipPrevious } from "react-icons/bi";

function App() {
  const videoRef = useRef(null);
  const [isPlaying, setIsPlaying] = useState(false);
}

Now define the play/pause function:

const handlePlay = () => {
  if (isPlaying) {
    videoRef.current.pause();
    setIsPlaying(false);
  } else {
    videoRef.current.play();
    setIsPlaying(true);
  }
};

Styling the Player

Here’s some CSS to make things look good:

.container {
  display: flex;
  margin: 0 auto;
  justify-content: center;
  width: 80%;
}

.playerContainer {
  display: flex;
  flex-direction: column;
  margin-top: 5em;
  border-radius: 1em;
  overflow: hidden;
}

.videoPlayer {
  border-radius: 1em;
  z-index: -1;
}

.controlsContainer {
  margin-top: -3.5em;
}

.controls {
  display: flex;
  z-index: 1;
  color: white;
  width: 200px;
  justify-content: space-between;
}

.controlButton {
  border: none;
  background: none;
}

.timeline {
  width: 70%;
}

.duration {
  display: flex;
  justify-content: center;
  align-items: center;
}

.controlButton:hover {
  cursor: pointer;
}

Displaying Current Time and Duration

Add the following states:

const [currentTime, setCurrentTime] = useState([0, 0]);
const [currentTimeSec, setCurrentTimeSec] = useState();
const [duration, setDuration] = useState([0, 0]);
const [durationSec, setDurationSec] = useState();

Now use useEffect to track time:

useEffect(() => {
  const { min, sec } = sec2Min(videoRef.current.duration);
  setDurationSec(videoRef.current.duration);
  setDuration([min, sec]);

  const interval = setInterval(() => {
    const { min, sec } = sec2Min(videoRef.current.currentTime);
    setCurrentTimeSec(videoRef.current.currentTime);
    setCurrentTime([min, sec]);
  }, 1000);

  return () => clearInterval(interval);
}, [isPlaying]);

Helper function to convert seconds to minutes:

const sec2Min = (sec) => {
  const min = Math.floor(sec / 60);
  const secRemain = Math.floor(sec % 60);
  return { min, sec: secRemain };
};

Display time in the UI:

<div className="duration">
  {currentTime[0]}:{currentTime[1]} / {duration[0]}:{duration[1]}
</div>

Adding a Timeline Range Slider

<input
  type="range"
  min="0"
  max={durationSec}
  defaultValue="0"
  value={currentTimeSec}
  className="timeline"
  onChange={(e) => {
    videoRef.current.currentTime = e.target.value;
  }}
/>

Live Demo

Try out the full working example in this CodeSandbox:

{% codesandbox immutable-sun-1bptx8 %}


Bonus: More Features to Add

You can extend the player with additional features like:


Conclusion

We’ve built a custom video player in React with features like play/pause, duration tracking, and a custom timeline. This is a great project to level up your React skills and understand DOM video handling.

Thanks for reading, and happy coding! 🚀


Build by Suraj Vishwakarma.