How to Create a Video Player in React

 Something that intrigued me most as of late is making a completely altered video player. Clearly, these days we have administrations that give gadgets to be utilized on our sites. 

Then again, you as of now have conditions that you can introduce and begin utilizing. Be that as it may, these offices include some significant downfalls, which for this situation is the nonappearance or trouble of customization. 

That is the reason I had making my own video player and obviously it's not as troublesome as I had suspected and in the end I thought that it was enjoyable. 

Precisely thus I had composing this article, to clarify bit by bit how to make a basic video player, yet with a similar rationale you can go a lot further.

In the present model we will utilize this video, it has sound and is totally free.

We should code 

Today we won't utilize any outside conditions, so you will be completely acquainted with everything. 

Concerning styling, toward the end I will give the CSS code, this is on the grounds that the focal point of the article is to show the rationale behind how the video player functions. 

The first thing I ask you is to download the video mentioned above and then rename the file to video.mp4. Finally create a folder in your project called assets and drag the file into that folder.

So we don't have the code in a solitary document, how about we make our own snare that will be responsible for controlling the whole activity of our video player.

// @src/hooks/useVideoPlayer.js
const useVideoPlayer = () => {
  // ...
};
export default useVideoPlayer;

In our hook we are going to use only two React hooks, useState() and useEffect().


// @src/hooks/useVideoPlayer.js
import { useState, useEffect } from "react";
const useVideoPlayer = () => {
  // ...
};
export default useVideoPlayer;

Now we can start creating our state, which we'll call playerState. This state of ours will have four properties, isPlaying, isMuted, progress and speed.

// @src/hooks/useVideoPlayer.js
import { useState, useEffect } from "react";
const useVideoPlayer = () => {
  const [playerState, setPlayerState] = useState({
    isPlaying: false,
    progress: 0,
    speed: 1,
    isMuted: false,
  });
  // ...
};
export default useVideoPlayer;

One thing I want you to keep in mind is that our hook has to take a single argument which in this case will be the reference of our video, which we're going to name videoElement.

// @src/hooks/useVideoPlayer.js
import { useState, useEffect } from "react";
const useVideoPlayer = (videoElement) => {
  const [playerState, setPlayerState] = useState({
    isPlaying: false,
    progress: 0,
    speed: 1,
    isMuted: false,
  });
  // ...
};
export default useVideoPlayer;

Now we can create our function that will dictate if the player is paused or not. For that, we'll keep the values of all the other properties of our playerState and we'll just say that whenever the function is executed it's to provide an inverse value of the current state of isPlaying.

// @src/hooks/useVideoPlayer.js
import { useState, useEffect } from "react";
const useVideoPlayer = (videoElement) => {
  const [playerState, setPlayerState] = useState({
    isPlaying: false,
    progress: 0,
    speed: 1,
    isMuted: false,
  });
  const togglePlay = () => {
    setPlayerState({
      ...playerState,
      isPlaying: !playerState.isPlaying,
    });
  };
  // ...
};
export default useVideoPlayer;

Now we need to use useEffect() to pause or not the video through the value of the isPlaying property.

// @src/hooks/useVideoPlayer.js
import { useState, useEffect } from "react";
const useVideoPlayer = (videoElement) => {
  const [playerState, setPlayerState] = useState({
    isPlaying: false,
    progress: 0,
    speed: 1,
    isMuted: false,
  });
  const togglePlay = () => {
    setPlayerState({
      ...playerState,
      isPlaying: !playerState.isPlaying,
    });
  };
  useEffect(() => {
    playerState.isPlaying
      ? videoElement.current.play()
      : videoElement.current.pause();
  }, [playerState.isPlaying, videoElement]);
  // ...
};
export default useVideoPlayer;

Now we have to create a function to help us know the video's progress, ie, by the duration of the video, we want the progress bar to show how much of the video we've seen.

For this we will create a function called handleOnTimeUpdate() so that we can calculate how much we have seen of the video with what remains to be seen. Afterwards we will keep the values of all the other properties of our state and we will only update the progress value.

// @src/hooks/useVideoPlayer.js
import { useState, useEffect } from "react";
const useVideoPlayer = (videoElement) => {
  const [playerState, setPlayerState] = useState({
    isPlaying: false,
    progress: 0,
    speed: 1,
    isMuted: false,
  });
  const togglePlay = () => {
    setPlayerState({
      ...playerState,
      isPlaying: !playerState.isPlaying,
    });
  };
  useEffect(() => {
    playerState.isPlaying
      ? videoElement.current.play()
      : videoElement.current.pause();
  }, [playerState.isPlaying, videoElement]);
  const handleOnTimeUpdate = () => {
    const progress = (videoElement.current.currentTime / videoElement.current.duration) * 100;
    setPlayerState({
      ...playerState,
      progress,
    });
  };
  // ...
};
export default useVideoPlayer;

One of the things we're going to want to implement is the possibility that we can drag the progress bar so we can choose where we want to view the video.

This way we will create a function called handleVideoProgress() which will have a single argument which in this case will be the event.

Then we will convert our event value from string to number. This is because then we want to tell our videoElement directly that the current viewing time is equal to the value of our manual change. Finally, we just keep the values of all the other properties of our state and we update only the progress.

// @src/hooks/useVideoPlayer.js
import { useState, useEffect } from "react";
const useVideoPlayer = (videoElement) => {
  const [playerState, setPlayerState] = useState({
    isPlaying: false,
    progress: 0,
    speed: 1,
    isMuted: false,
  });
  const togglePlay = () => {
    setPlayerState({
      ...playerState,
      isPlaying: !playerState.isPlaying,
    });
  };
  useEffect(() => {
    playerState.isPlaying
      ? videoElement.current.play()
      : videoElement.current.pause();
  }, [playerState.isPlaying, videoElement]);
  const handleOnTimeUpdate = () => {
    const progress = (videoElement.current.currentTime / videoElement.current.duration) * 100;
    setPlayerState({
      ...playerState,
      progress,
    });
  };
  const handleVideoProgress = (event) => {
    const manualChange = Number(event.target.value);
    videoElement.current.currentTime = (videoElement.current.duration / 100) * manualChange;
    setPlayerState({
      ...playerState,
      progress: manualChange,
    });
  };
  // ...
};
export default useVideoPlayer;

Another feature that we will want to implement is the video playback speed, this because I believe that not everyone is 1.0x fans and that there are guys that watch videos at 1.25x.

For that we will create a function called handleVideoSpeed() that will receive an event as a single argument, then the value of that event will be converted to number and finally we will tell the videoElement that the playback rate is equal to the event value.

In our state we keep the values of all properties except speed.

// @src/hooks/useVideoPlayer.js
import { useState, useEffect } from "react";
const useVideoPlayer = (videoElement) => {
  const [playerState, setPlayerState] = useState({
    isPlaying: false,
    progress: 0,
    speed: 1,
    isMuted: false,
  });
  const togglePlay = () => {
    setPlayerState({
      ...playerState,
      isPlaying: !playerState.isPlaying,
    });
  };
  useEffect(() => {
    playerState.isPlaying
      ? videoElement.current.play()
      : videoElement.current.pause();
  }, [playerState.isPlaying, videoElement]);
  const handleOnTimeUpdate = () => {
    const progress = (videoElement.current.currentTime / videoElement.current.duration) * 100;
    setPlayerState({
      ...playerState,
      progress,
    });
  };
  const handleVideoProgress = (event) => {
    const manualChange = Number(event.target.value);
    videoElement.current.currentTime = (videoElement.current.duration / 100) * manualChange;
    setPlayerState({
      ...playerState,
      progress: manualChange,
    });
  };
  const handleVideoSpeed = (event) => {
    const speed = Number(event.target.value);
    videoElement.current.playbackRate = speed;
    setPlayerState({
      ...playerState,
      speed,
    });
  };
  // ...
};
export default useVideoPlayer;

The last feature I want to add is the ability to mute and unmute the video. And how you should calculate the logic is very similar to play/pause.

// @src/hooks/useVideoPlayer.js
import { useState, useEffect } from "react";
const useVideoPlayer = (videoElement) => {
  const [playerState, setPlayerState] = useState({
    isPlaying: false,
    progress: 0,
    speed: 1,
    isMuted: false,
  });
  const togglePlay = () => {
    setPlayerState({
      ...playerState,
      isPlaying: !playerState.isPlaying,
    });
  };
  useEffect(() => {
    playerState.isPlaying
      ? videoElement.current.play()
      : videoElement.current.pause();
  }, [playerState.isPlaying, videoElement]);
  const handleOnTimeUpdate = () => {
    const progress = (videoElement.current.currentTime / videoElement.current.duration) * 100;
    setPlayerState({
      ...playerState,
      progress,
    });
  };
  const handleVideoProgress = (event) => {
    const manualChange = Number(event.target.value);
    videoElement.current.currentTime = (videoElement.current.duration / 100) * manualChange;
    setPlayerState({
      ...playerState,
      progress: manualChange,
    });
  };
  const handleVideoSpeed = (event) => {
    const speed = Number(event.target.value);
    videoElement.current.playbackRate = speed;
    setPlayerState({
      ...playerState,
      speed,
    });
  };
  const toggleMute = () => {
    setPlayerState({
      ...playerState,
      isMuted: !playerState.isMuted,
    });
  };
  useEffect(() => {
    playerState.isMuted
      ? (videoElement.current.muted = true)
      : (videoElement.current.muted = false);
  }, [playerState.isMuted, videoElement]);
  // ...
};
export default useVideoPlayer;
Finally, just return our state and all the functions that were created.
// @src/hooks/useVideoPlayer.js
import { useState, useEffect } from "react";
const useVideoPlayer = (videoElement) => {
  const [playerState, setPlayerState] = useState({
    isPlaying: false,
    progress: 0,
    speed: 1,
    isMuted: false,
  });
  const togglePlay = () => {
    setPlayerState({
      ...playerState,
      isPlaying: !playerState.isPlaying,
    });
  };
  useEffect(() => {
    playerState.isPlaying
      ? videoElement.current.play()
      : videoElement.current.pause();
  }, [playerState.isPlaying, videoElement]);
  const handleOnTimeUpdate = () => {
    const progress = (videoElement.current.currentTime / videoElement.current.duration) * 100;
    setPlayerState({
      ...playerState,
      progress,
    });
  };
  const handleVideoProgress = (event) => {
    const manualChange = Number(event.target.value);
    videoElement.current.currentTime = (videoElement.current.duration / 100) * manualChange;
    setPlayerState({
      ...playerState,
      progress: manualChange,
    });
  };
  const handleVideoSpeed = (event) => {
    const speed = Number(event.target.value);
    videoElement.current.playbackRate = speed;
    setPlayerState({
      ...playerState,
      speed,
    });
  };
  const toggleMute = () => {
    setPlayerState({
      ...playerState,
      isMuted: !playerState.isMuted,
    });
  };
  useEffect(() => {
    playerState.isMuted
      ? (videoElement.current.muted = true)
      : (videoElement.current.muted = false);
  }, [playerState.isMuted, videoElement]);
  return {
    playerState,
    togglePlay,
    handleOnTimeUpdate,
    handleVideoProgress,
    handleVideoSpeed,
    toggleMute,
  };
};
export default useVideoPlayer;

Now we can start working on our App.jsx component and for the record the icon library used was Boxicons and the typography was DM Sans.

First I will give the css code of our App.css.

body {
  background: #EEEEEE;
}
.container {
  display: flex;
  justify-content: center;
  align-items: center;
  height: 100vh;
}
h1 {
  color: white;
}
video {
  width: 100%;
}
.video-wrapper {
  width: 100%;
  max-width: 700px;
  position: relative;
  display: flex;
  justify-content: center;
  overflow: hidden;
  border-radius: 10px;
}
.video-wrapper:hover .controls {
  transform: translateY(0%);
}
.controls {
  display: flex;
  align-items: center;
  justify-content: space-evenly;
  position: absolute;
  bottom: 30px;
  padding: 14px;
  width: 100%;
  max-width: 500px;
  flex-wrap: wrap;
  background: rgba(255, 255, 255, 0.25);
  box-shadow: 0 8px 32px 0 rgba(255, 255, 255, 0.1);
  backdrop-filter: blur(4px);
  -webkit-backdrop-filter: blur(4px);
  border-radius: 10px;
  border: 1px solid rgba(255, 255, 255, 0.18);
  transform: translateY(150%);
  transition: all 0.3s ease-in-out;
}
.actions button {
  background: none;
  border: none;
  outline: none;
  cursor: pointer;
}
.actions button i {
  background-color: none;
  color: white;
  font-size: 30px;
}
input[type="range"] {
  -webkit-appearance: none !important;
  background: rgba(255, 255, 255, 0.2);
  border-radius: 20px;
  height: 4px;
  width: 350px;
}
input[type="range"]::-webkit-slider-thumb {
  -webkit-appearance: none !important;
  cursor: pointer;
  height: 6px;
}
input[type="range"]::-moz-range-progress {
  background: white;
}
.velocity {
  appearance: none;
  background: none;
  color: white;
  outline: none;
  border: none;
  text-align: center;
  font-size: 16px;
}
.mute-btn {
  background: none;
  border: none;
  outline: none;
  cursor: pointer;
}
.mute-btn i {
  background-color: none;
  color: white;
  font-size: 20px;
}

Now we can start working on our component and for that we'll import everything we need, in this case it's our styling, our video and our hook.

// @src/App.jsx
import React from "react";
import "./App.css";
import video from "./assets/video.mp4";
import useVideoPlayer from "./hooks/useVideoPlayer";
const App = () => {
  // ...
};
export default App;
Then we'll import the useRef() hook to create our videoElement's reference. Like this:
// @src/App.jsx
import React, { useRef } from "react";
import "./App.css";
import video from "./assets/video.mp4";
import useVideoPlayer from "./hooks/useVideoPlayer";
const App = () => {
  const videoElement = useRef(null);
  // ...
};
export default App;
Then we can get our playerState and each of our functions from our hook. Like this:
// @src/App.jsx
import React, { useRef } from "react";
import "./App.css";
import video from "./assets/video.mp4";
import useVideoPlayer from "./hooks/useVideoPlayer";
const App = () => {
  const videoElement = useRef(null);
  const {
    playerState,
    togglePlay,
    handleOnTimeUpdate,
    handleVideoProgress,
    handleVideoSpeed,
    toggleMute,
  } = useVideoPlayer(videoElement);
  // ...
};
export default App;

Now we can finally start working on our template, this way we will start working our video element which will have three props, the source will be our video and we will still pass our reference and our handleOnTimeUpdate() function.

// @src/App.jsx
import React, { useRef } from "react";
import "./App.css";
import video from "./assets/video.mp4";
import useVideoPlayer from "./hooks/useVideoPlayer";
const App = () => {
  const videoElement = useRef(null);
  const {
    playerState,
    togglePlay,
    handleOnTimeUpdate,
    handleVideoProgress,
    handleVideoSpeed,
    toggleMute,
  } = useVideoPlayer(videoElement);
  return (
    <div className="container">
      <div className="video-wrapper">
        <video
          src={video}
          ref={videoElement}
          onTimeUpdate={handleOnTimeUpdate}
        />
        // ...
      </div>
    </div>
  );
};
export default App;

Now we can start working on our video controls, let's start with the play and pause button. To which we will pass the togglePlay() function and we will do a conditional rendering, so that it shows the indicated icons according to the value of the isPlaying property.

// @src/App.jsx
import React, { useRef } from "react";
import "./App.css";
import video from "./assets/video.mp4";
import useVideoPlayer from "./hooks/useVideoPlayer";
const App = () => {
  const videoElement = useRef(null);
  const {
    playerState,
    togglePlay,
    handleOnTimeUpdate,
    handleVideoProgress,
    handleVideoSpeed,
    toggleMute,
  } = useVideoPlayer(videoElement);
  return (
    <div className="container">
      <div className="video-wrapper">
        <video
          src={video}
          ref={videoElement}
          onTimeUpdate={handleOnTimeUpdate}
        />
        <div className="controls">
          <div className="actions">
            <button onClick={togglePlay}>
              {!playerState.isPlaying ? (
                <i className="bx bx-play"></i>
              ) : (
                <i className="bx bx-pause"></i>
              )}
            </button>
          </div>
          // ...
        </div>
      </div>
    </div>
  );
};
export default App;

Now we can start by working on our input, which will be of the range type, which will have a minimum value of zero and a maximum value of one hundred. In the same way we will pass the handleVideoProgress() function and the value of the progress property.

// @src/App.jsx
import React, { useRef } from "react";
import "./App.css";
import video from "./assets/video.mp4";
import useVideoPlayer from "./hooks/useVideoPlayer";
const App = () => {
  const videoElement = useRef(null);
  const {
    playerState,
    togglePlay,
    handleOnTimeUpdate,
    handleVideoProgress,
    handleVideoSpeed,
    toggleMute,
  } = useVideoPlayer(videoElement);
  return (
    <div className="container">
      <div className="video-wrapper">
        <video
          src={video}
          ref={videoElement}
          onTimeUpdate={handleOnTimeUpdate}
        />
        <div className="controls">
          <div className="actions">
            <button onClick={togglePlay}>
              {!playerState.isPlaying ? (
                <i className="bx bx-play"></i>
              ) : (
                <i className="bx bx-pause"></i>
              )}
            </button>
          </div>
          <input
            type="range"
            min="0"
            max="100"
            value={playerState.progress}
            onChange={(e) => handleVideoProgress(e)}
          />
          // ...
        </div>
      </div>
    </div>
  );
};
export default App;

Now we are going to work on the element to select our video playback speed. To which we will pass the value of the speed property and handleVideoSpeed() function.

// @src/App.jsx
import React, { useRef } from "react";
import "./App.css";
import video from "./assets/video.mp4";
import useVideoPlayer from "./hooks/useVideoPlayer";
const App = () => {
  const videoElement = useRef(null);
  const {
    playerState,
    togglePlay,
    handleOnTimeUpdate,
    handleVideoProgress,
    handleVideoSpeed,
    toggleMute,
  } = useVideoPlayer(videoElement);
  return (
    <div className="container">
      <div className="video-wrapper">
        <video
          src={video}
          ref={videoElement}
          onTimeUpdate={handleOnTimeUpdate}
        />
        <div className="controls">
          <div className="actions">
            <button onClick={togglePlay}>
              {!playerState.isPlaying ? (
                <i className="bx bx-play"></i>
              ) : (
                <i className="bx bx-pause"></i>
              )}
            </button>
          </div>
          <input
            type="range"
            min="0"
            max="100"
            value={playerState.progress}
            onChange={(e) => handleVideoProgress(e)}
          />
          <select
            className="velocity"
            value={playerState.speed}
            onChange={(e) => handleVideoSpeed(e)}
          >
            <option value="0.50">0.50x</option>
            <option value="1">1x</option>
            <option value="1.25">1.25x</option>
            <option value="2">2x</option>
          </select>
          // ...
        </div>
      </div>
    </div>
  );
};
export default App;

Last but not least we will have the button that will be responsible for mute and unmute the video. To which we will pass the toggleMute() function and we will do conditional rendering to show the indicated icons according to the isMuted property.

// @src/App.jsx
import React, { useRef } from "react";
import "./App.css";
import video from "./assets/video.mp4";
import useVideoPlayer from "./hooks/useVideoPlayer";
const App = () => {
  const videoElement = useRef(null);
  const {
    playerState,
    togglePlay,
    handleOnTimeUpdate,
    handleVideoProgress,
    handleVideoSpeed,
    toggleMute,
  } = useVideoPlayer(videoElement);
  return (
    <div className="container">
      <div className="video-wrapper">
        <video
          src={video}
          ref={videoElement}
          onTimeUpdate={handleOnTimeUpdate}
        />
        <div className="controls">
          <div className="actions">
            <button onClick={togglePlay}>
              {!playerState.isPlaying ? (
                <i className="bx bx-play"></i>
              ) : (
                <i className="bx bx-pause"></i>
              )}
            </button>
          </div>
          <input
            type="range"
            min="0"
            max="100"
            value={playerState.progress}
            onChange={(e) => handleVideoProgress(e)}
          />
          <select
            className="velocity"
            value={playerState.speed}
            onChange={(e) => handleVideoSpeed(e)}
          >
            <option value="0.50">0.50x</option>
            <option value="1">1x</option>
            <option value="1.25">1.25x</option>
            <option value="2">2x</option>
          </select>
          <button className="mute-btn" onClick={toggleMute}>
            {!playerState.isMuted ? (
              <i className="bx bxs-volume-full"></i>
            ) : (
              <i className="bx bxs-volume-mute"></i>
            )}
          </button>
        </div>
      </div>
    </div>
  );
};
export default App;

The end result should look like this:

Post a Comment

0 Comments