owned this note
owned this note
Published
Linked with GitHub
# How to build a Video Player using React Player and Material Ui.
Video assets are quickly taking over as the preferred method of product promotion on websites. Thus, it is essential for developers to understand how to create and modify video players to meet the requirements of their websites.
In this article, I'll demonstrate how to develop a video player using the [react-player](https://www.npmjs.com/package/react-player) package, and style it with [Material UI](https://mui.com/) so you can make a video player that perfectly suits the requirements of your next web build.
This article's main purpose is to assist readers in creating and customizing their video players in accordance with their preferences. However, if you prefer a program that you can simply install to gain access to a fully developed and personalized video player, take a look at the [Mui player](https://muiplayer.js.org/). Also, be aware that this service has drawbacks, including the inability to adjust the video player to your preferences.
You must have a solid understanding of React's hooks and props in addition to a working knowledge of React.js to follow along with this article.
To quickly track issues, I'll also suggest that you use [React Developer Tools](https://chrome.google.com/webstore/detail/react-developer-tools/fmkadmapgofadopljbjfkapdkoienihi?hl=en).
## What is React Player?
React player is a react component used in rendering various kinds of video assets, including YouTube, Facebook, Twitch, SoundCloud, Streamable, Vimeo, Wistia, Mixcloud, DailyMotion, and so on. React player is a package used to create video players that supports various video sources and types.
## Creating the Video Player
The first thing to do is to bootstrap a new React project using the Create React App (CRA) bundler. Inside the project folder, install the needed dependencies.
### Installing the Needed Dependencies
First, we have to install the [react-player](https://www.npmjs.com/package/react-player) by typing the code below into your project terminal:
```
npm i react-player
```
Next, we have to install [Material UI](https://mui.com/) for styling the video player:
```
npm install @mui/material @emotion/react @emotion/styled
```
Still on [Material UI](https://mui.com/), we have to install the SVG icons. Icons are the major thing we want to import from Material UI. Use the code below to import and have access to the material UI icons:
```
npm install @mui/icons-material
```
And lastly, we would install [screenfull](https://www.npmjs.com/package/screenfull), the package for bringing our video player into full-screen mode.
```
npm i screenfull
```
### Setting up Your Application
You have to first clean up the default folder structure for the build by deleting the logo file and removing everything in the return block inside the App.js file.
Your App.js file should look like this:
```
import * as React from 'react';
import './App.css';
function App() {
return (
<>
</>
);
}
export default App;
```
Next, import the react-player and screenfull in your App.js file.
```
import ReactPlayer from 'react-player';
import screenfull from 'screenfull';
```
Let’s create a simple header for the web app by updating our App.js file with this:
```
import './App.css';
import ReactPlayer from 'react-player';
import screenfull from 'screenfull';
function App() {
return (
<>
<header className='header__section'>
<p className='header__text'>Build a Video Player - Tutorial</p>
</header>
</>
);
}
export default App;
```
Navigate to your App.css file and add the code below to style the header:
```
* {
margin: 0;
box-sizing: border-box;
}
.header__section {
text-align: center;
justify-content: center;
align-items: center;
height: 40px;
background-color: rgb(70, 70, 172);
}
.header__text {
color: white;
font-size: 28px;
font-weight: 800;
font-family: Georgia, 'Times New Roman', Times, serif;
padding-top: 5px;
padding-bottom: 5px;
}
```
Note, clean up the App.css file before adding the code above.
Now, we want to get our video URL and preview the video using the react-player package.
[Here](http://commondatastorage.googleapis.com/gtv-videos-bucket/sample/TearsOfSteel.mp4) is the link to the video sample we will use in this article, and we get the link from this [github gist sample for free video URLs](https://gist.github.com/deepakpk009/99fd994da714996b296f11c3c371d5ee).
In the App.js file, import the `container` wrapper from the Material UI, which is where we'll include our React player and Controls.
```
import * as React from 'react';
import './App.css';
import ReactPlayer from 'react-player';
import screenfull from 'screenfull';
import Container from '@mui/material/Container';
function App() {
return (
<>
<header className='header__section'>
<p className='header__text'>Build a Video Player - Tutorial</p>
</header>
<Container maxWidth="md">
<div className='playerDiv'>
<ReactPlayer width={'100%'} height='100%' url="http://commondatastorage.googleapis.com/gtv-videos-bucket/sample/TearsOfSteel.mp4"/>
</div>
</Container>
</>
);
}
export default App;
```
In the code snippet above, we have a `Container wrapper`, where we embedded our react-player component. We also passed the video sample link as the value for the `url` prop. In order for the player to cover the whole `Container wrapper`, we also gave it a fixed width and height of 100%.
Navigate to your App.css file and style the Player Div using the code below:
```
.playerDiv {
width:100%;
position: relative;
}
```
If you run `npm start` to preview the video player on browser, you would see this:

In the snapshot above, the react-player renders our sample video. Still, there seems not to be any video displayed, because we’ve not added the needed props for that functionality.
To fix that, add a playing prop and a muted prop to the `ReactPlayer` component, and set the props to true, as shown in the code below:
```
<ReactPlayer width={'100%'} height='100%'
url="http://commondatastorage.googleapis.com/gtv-videos-bucket/sample/TearsOfSteel.mp4"
playing={true}
muted={true}
/>
```
In the code above, we included the playing and muted props to enable our sample video to play. In place of the props' default values of false, we set a value of true. The code above produces the playing video seen in the illustration below:

Since there are no controls present, the video player is not interactive. Controls, a prop in the react-player package, has the value false by default. The video player's controls are automatically added when the value is updated to true.

The `controls` prop adds the controls in the image above to your video player. But some controls we want our video player to have are not part of the controls react-player provides. For example, we don’t have the fast forward and rewind features, hence the reason for customizing ours.
## The Controls
### Video Player Functionalities
The picture below is a visual representation of the functionalities of our Video Player.

From the image above, we can see that this video player has the following features:
- Movie title
- Pause and Play
- Fast Forward and Rewind
- Muting
- Volume increase and decrease
- Playback Speed
- Fullscreen Mode
### Importing and Styling the Controls
Create a new folder called `Components` in the src folder. Inside the newly created folder, create two new files-`ControlIcons.jsx` and `ControlIcons.css`.
The controls are divided into three sections: the top, middle, and bottom.
Let’s get into the code:
First, import the ControlIcons component into the App.js file by updating your `App.js` file with the following code:
```
import * as React from 'react';
import './App.css';
import ReactPlayer from 'react-player';
import screenfull from 'screenfull';
import Container from '@mui/material/Container';
import ControlIcons from './Components/ControlIcons';
function App() {
return (
<>
<header className='header__section'>
<p className='header__text'>Build a Video Player - Tutorial</p>
</header>
<Container maxWidth="md">
<div className='playerDiv'>
<ReactPlayer width={'100%'} height='100%'
url="http://commondatastorage.googleapis.com/gtv-videos-bucket/sample/TearsOfSteel.mp4"
playing={true}
muted={true}
controls={true}
/>
<ControlIcons/>
</div>
</Container>
</>
);
}
export default App;
```
Next, navigate to ControlIcons.jsx file, and let’s get right into importing the controls segment by segment.
Update the code in the `ContolIcons.jsx` starting file as follows:
```
import React from 'react';
import './ControlIcons.css';
const ControlIcons = () => {
return (
<div className="controls__div">
</div>
)
}
export default ControlIcons;
```
We must apply some styles, such as the position of absolute, to the div with a className of `controls__div` in order to allow our controls to overlap the video.
Inside the ControlsIcons.css file, add this:
```
.controls__div {
position:absolute;
top: 0;
bottom: 0;
left: 0;
right: 0;
display: flex;
flex-direction: column;
justify-content: space-between;
z-index: 2;
background: rgba(0,0,0,0.6);
}
```
In the code above, we set the position of the div with the className of `controls__div` as `position: absolute` in order for it to position properly inside the div with the className of `playerDiv` in our App.js file.
We would use the `Grid` wrapper from Material UI for our style. We now begin with the top section, where we only want the word "Player" to be visible.
For this, we have to first import Grid and Typography (which is where the text to be displayed is written, this is the way to display a text when using material UI for styling).
```
import React from 'react';
import './ControlIcons.css';
import Grid from '@mui/material/Grid';
import Typography from '@mui/material/Typography';
const ControlIcons = () => {
return (
<div className="controls__div">
</div>
)
}
export default ControlIcons;
```
The following code should be added to your `ControlIcons.jsx` file in order to display the top section text (which is "Player"):
```
import React from 'react';
import './ControlIcons.css';
import Grid from '@mui/material/Grid';
import Typography from '@mui/material/Typography';
const ControlIcons = () => {
return (
<div className="controls__div">
{/* Top Segment */}
<Grid container direction='row' alignItems='center' justifyContent='start' style={{padding: 16 }}>
<Grid item>
<Typography variant='h5' style={{color:'white'}}>Player</Typography>
</Grid>
</Grid>
</div>
)
}
export default ControlIcons;
```
In the code snippet above, we employ a Grid wrapper that acts as a container to hold the text "Player". This Grid was given some positional parameters, and we then added the inline style `padding: 16px` to it. The item to be placed inside the container was then represented by another Grid wrapper, and the text was then displayed using the `Typography` wrapper.
Let’s move on to the middle section. Here we have three buttons: rewind, play, and fast-forward.
As usual, the first step is to import the needed icons. Add the following imports to your ControlIcons.jsx file.
```
import { IconButton } from '@mui/material';
import { FastRewind } from '@mui/icons-material';
import { FastForwardSharp } from '@mui/icons-material';
import { PlayArrowSharp } from '@mui/icons-material';
```
Next, let’s make use of the icons that we imported. Do this:
```
<Grid container direction='row' alignItems='center' justifyContent='center'>
<IconButton className='controls__icons' aria-label='reqind'>
<FastRewind fontSize='large' style={{color:'white'}}/>
</IconButton>
<IconButton className='controls__icons' aria-label='reqind'>
<PlayArrowSharp fontSize='large' style={{color:'white'}}/>
</IconButton>
<IconButton className='controls__icons' aria-label='reqind'>
<FastForwardSharp fontSize='large' style={{color:'white'}}/>
</IconButton>
</Grid>
```
What we simply did in the code above was to create a Grid (Container) where all three icons live, we applied some value to this Grid which determines how the icons are positioned. And lastly, we displayed all three icons using the `IconButton wrapper` that Material UI provides. For each of the IconButton wrappers, we added a className called `controls__icons`.
In the code above, all three icons are contained in a Grid (Container), and a value is applied to this Grid to determine how the icons are positioned. And lastly, we displayed all three icons using the `IconButton wrapper` that Material UI provides. For each of the `IconButton` wrappers, we added a className called `controls__icons`.
Here is the style for the div with className of `controls__icons`:
```
.controls__icons {
color: white;
font-size: 50;
transform:scale(0.9);
}
.controls__icons:hover {
color: #fff;
transform:scale(1);
}
```
And lastly, we move to the third section. This section has a sub-sections: the title of the video, the player slider, and the last group of controls.
First, import the icons using the following code:
```
import Button from '@mui/material/Button';
import Slider from '@mui/material/Slider';
import { styled } from '@mui/material/styles';
import { VolumeUp } from '@mui/icons-material';
import { Fullscreen } from '@mui/icons-material';
```
We then add the code for the first category (the video title) in this section and create the Grid (container) that houses the entire bottom segment.
Here is the code to execute the previous statement:
```
{/* Bottom Section */}
<Grid container direction='row' alignItems='center' justifyContent='space-between' style={{padding: 16}}>
<Grid item>
<Typography variant='h5' style={{color:'white'}}>Tears Of Steel</Typography>
</Grid>
</Grid>
```
From the code above, it can be derived that this bottom section is embedded inside a container given a direction of `row` and justifyContent of `space-between` which renders the bottom component with equal space in between all the categories.
Let's go on to the next category (the video player slider). For this, we'll just use the 'PrettoSlider' pre-built slider from the material UI. There is a function needed to use the slider. We will use the PrettoSlider as a component wherever we want to display it after adding the function from the code snippet below to your "ControlIcons.jsx" file.
PrettoSlider Function:
```
const PrettoSlider = styled(Slider)({
height: 5,
'& .MuiSlider-track': {
border: 'none',
},
'& .MuiSlider-thumb': {
height: 16,
width: 16,
backgroundColor: '#fff',
border: '2px solid currentColor',
'&:focus, &:hover, &.Mui-active, &.Mui-focusVisible': {
boxShadow: 'inherit',
},
'&:before': {
display: 'none',
},
},
'& .MuiSlider-valueLabel': {
lineHeight: 1.2,
fontSize: 12,
background: 'unset',
padding: 0,
width: 32,
height: 32,
borderRadius: '50% 50% 50% 0',
backgroundColor: '#52af77',
transformOrigin: 'bottom left',
transform: 'translate(50%, -100%) rotate(-45deg) scale(0)',
'&:before': { display: 'none' },
'&.MuiSlider-valueLabelOpen': {
transform: 'translate(50%, -100%) rotate(-45deg) scale(1)',
},
'& > *': {
transform: 'rotate(45deg)',
},
},
});
```
Now use the PrettoSlider as a component:
```
{/* Bottom Section */}
<Grid container direction='row' alignItems='center' justifyContent='space-between' style={{padding: 16}}>
<Grid item>
<Typography variant='h5' style={{color:'white'}}>Tears Of Steel</Typography>
</Grid>
<Grid item xs={12}>
<PrettoSlider
min={0}
max={100}
defaultValue={20}
/>
<Grid container direction='row' justifyContent='space-between'>
<Typography variant='h7' style={{color:'white'}}>00:26</Typography>
<Typography variant='h7' style={{color:'white'}}>12:30</Typography>
</Grid>
</Grid>
</Grid>
```
We provided the PrettoSlider with certain props, including a default Value, the terms "min" for minimum value and "max" for maximum value. Next, we added some digits to show the video's duration and total watch time.
The play button, volume up, volume slider, playback speed, and full screen are the icons found in the last category.
We would use the "IconButton wrapper" to display the icons, and we would also render the volume slider by giving it the className "volume slider" and some parameters with default values.
```
<Grid item>
<Grid container alignItems='center' direction='row'>
<IconButton className='controls__icons' aria-label='reqind'>
<PlayArrowSharp fontSize='large' style={{color:'white'}}/>
</IconButton>
<IconButton className='controls__icons' aria-label='reqind'>
<VolumeUp fontSize='large' style={{color:'white'}}/>
</IconButton>
<Typography style={{color:'#fff', paddingTop:'5px'}}>40</Typography>
<Slider
min={0}
max={100}
defaultValue={100}
className='volume__slider'
/>
</Grid>
</Grid>
```
Here is the code to style the Volume Slider:
```
.volume__slider {
width: 100;
margin-top: -50px;
margin-left: 130px;
}
```
Now let’s add the playback speed rate and the full-screen icon. Using the code below, display the text and icon in your video player.
```
<Grid item>
<Button variant='text' className='bottom__icons'>
<Typography>1X</Typography>
</Button>
<IconButton className='bottom__icons'>
<Fullscreen fontSize='large'/>
</IconButton>
</Grid>
```
The code snippet above should be added to the `ControlIcons.jsx` file inside the `grid container` for the bottom section and the styles should be in the `ControlIcons.css` file.
Here is the style for the className of bottom__icons:
```
.bottom__icons {
color:#999;
}
.bottom__icons:hover {
color: white;
}
```
Now, when you preview the app, you ought to see a video player rendered with all the imported and styled buttons and icons.
In case you are running into errors at this point, simply crosscheck your code with the one below:
```
import React from 'react';
import './ControlIcons.css';
import Grid from '@mui/material/Grid';
import Typography from '@mui/material/Typography';
import { IconButton } from '@mui/material';
import { FastRewind } from '@mui/icons-material';
import { FastForwardSharp } from '@mui/icons-material';
import { PlayArrowSharp } from '@mui/icons-material';
import Button from '@mui/material/Button';
import Slider from '@mui/material/Slider';
import { styled } from '@mui/material/styles';
import { VolumeUp } from '@mui/icons-material';
import { Fullscreen } from '@mui/icons-material';
const ControlIcons = () => {
const PrettoSlider = styled(Slider)({
height: 5,
'& .MuiSlider-track': {
border: 'none',
},
'& .MuiSlider-thumb': {
height: 16,
width: 16,
backgroundColor: '#fff',
border: '2px solid currentColor',
'&:focus, &:hover, &.Mui-active, &.Mui-focusVisible': {
boxShadow: 'inherit',
},
'&:before': {
display: 'none',
},
},
'& .MuiSlider-valueLabel': {
lineHeight: 1.2,
fontSize: 12,
background: 'unset',
padding: 0,
width: 32,
height: 32,
borderRadius: '50% 50% 50% 0',
backgroundColor: '#52af77',
transformOrigin: 'bottom left',
transform: 'translate(50%, -100%) rotate(-45deg) scale(0)',
'&:before': { display: 'none' },
'&.MuiSlider-valueLabelOpen': {
transform: 'translate(50%, -100%) rotate(-45deg) scale(1)',
},
'& > *': {
transform: 'rotate(45deg)',
},
},
});
return (
<div className="controls__div">
{/* Top Segment */}
<Grid container direction='row' alignItems='center' justifyContent='start' style={{padding: 16 }}>
<Grid item>
<Typography variant='h5' style={{color:'white'}}>Player</Typography>
</Grid>
</Grid>
{/* Middle Segment */}
<Grid container direction='row' alignItems='center' justifyContent='center'>
<IconButton className='controls__icons' aria-label='reqind'>
<FastRewind fontSize='large' style={{color:'white'}}/>
</IconButton>
<IconButton className='controls__icons' aria-label='reqind'>
<PlayArrowSharp fontSize='large' style={{color:'white'}}/>
</IconButton>
<IconButton className='controls__icons' aria-label='reqind'>
<FastForwardSharp fontSize='large' style={{color:'white'}}/>
</IconButton>
</Grid>
{/* Bottom Segment */}
<Grid container direction='row' alignItems='center' justifyContent='space-between' style={{padding: 16}}>
<Grid item>
<Typography variant='h5' style={{color:'white'}}>Tears Of Steel</Typography>
</Grid>
<Grid item xs={12}>
<PrettoSlider
min={0}
max={100}
defaultValue={20}
/>
<Grid container direction='row' justifyContent='space-between'>
<Typography variant='h7' style={{color:'white'}}>00:26</Typography>
<Typography variant='h7' style={{color:'white'}}>12:30</Typography>
</Grid>
</Grid>
<Grid item>
<Grid container alignItems='center' direction='row'>
<IconButton className='controls__icons' aria-label='reqind'>
<PlayArrowSharp fontSize='large' style={{color:'white'}}/>
</IconButton>
<IconButton className='controls__icons' aria-label='reqind'>
<VolumeUp fontSize='large' style={{color:'white'}}/>
</IconButton>
<Typography style={{color:'#fff', paddingTop:'5px'}}>40</Typography>
<Slider
min={0}
max={100}
defaultValue={100}
className='volume__slider'
/>
</Grid>
</Grid>
<Grid item>
<Button variant='text' className='bottom__icons'>
<Typography>1X</Typography>
</Button>
<IconButton className='bottom__icons'>
<Fullscreen fontSize='large'/>
</IconButton>
</Grid>
</Grid>
</div>
)
}
export default ControlIcons;
```
When you preview, you should have a replica of the image below:

You'll see for yourself that the buttons are not interactive and must be activated if you try clicking on them. This brings us to the article's final section, `Activating the Controls`.
## Activating the Controls
We would proceed in this part in a logical order, beginning at the top. This means that we'll start by activating the controls for the middle part.
Navigate into the App.js file, we’ll start by creating a state called `playerState`. This state is an object with six properties: playing, mute, playerbackRate, played, volume, and seeking.
```
const [playerstate, setPlayerState] = useState({
playing: true,
mute: true,
volume: 0.5,
playerbackRate:1.0,
played:0,
seeking: false,
})
```
Note, in other for you to be able to use the `useState` hook, you need to import it at the top like this:
```
import { useState } from 'react';
```
Next, destructure the state to access its properties.
```
//Destructure State in other to get the values in it
const { playing, mute, volume, playerbackRate, played, seeking} = playerstate;
```
Update the React Player component by replacing the values for playing and muted props from true to playing and mute, respectively.
```
<ReactPlayer width={'100%'}
height='100%' url="http://commondatastorage.googleapis.com/gtv-videos-bucket/sample/TearsOfSteel.mp4"
playing={playing}
muted={mute}
/>
```
We now develop a feature that determines whether our video is playing or paused. For this, we'll use the spread operator to store the values of our current playerState, and anytime this function is called, we'll update the playing property in the state to return the inverse of its current value.
Here is the function:
```
const handlePlayAndPause = () => {
setPlayerState({
...playerstate,
playing: !playerstate.playing
})
}
```
We have to send the function as a prop to the ControlIcons component so that the play button may access this feature. Just like this, pass the function as a prop:
```
<ControlIcons playandpause={handlePlayAndPause} />
```
Destructure the prop in the ControlIcons Component. We are now left with making use of the prop gotten.
Update the `IconButton Wrapper` where the play button icon is embedded, with `onClick={playandpause}`:
```
<IconButton className='controls__icons' aria-label='reqind' onClick={playandpause}>
<PlayArrowSharp fontSize='large' style={{color:'white'}}/>
</IconButton>
```
Use the code above to update the icon's two instances.
Let's include the capability to switch between play and pause. The playing attribute from our "playerState" would be used to conditionally render the icons and import the pause button.
Import the Pause Icon.
```
import { PauseSharp } from '@mui/icons-material';
```
Pass the playing prop to the ControlIcons component
```
<ControlIcons playandpause={handlePlayAndPause} playing={playing}/>
```
Destructure the prop
```
const ControlIcons = ({ playandpause, playing }) => { }
```
Replace the two instances of the play icon wrapper with this:
```
<IconButton className='controls__icons' aria-label='reqind' onClick={playandpause}>
{
playing? (
<PauseSharp fontSize='large' style={{color:'white'}}/>
) : (
<PlayArrowSharp fontSize='large' style={{color:'white'}}/>
)
}
</IconButton>
```
In the code above, we used the ternary operator to conditionally render the icon based on the value of the playing attribute contained within the `playerState`. According to the rationale, the `play icon` should be rendered when the video is paused, and the `pause icon` should be rendered when the video is playing.
You should be able to play and pause the video now.
In order to manage the rewind and fast-forward features, we must now construct a new set of functions. Just like the `Windows Media Player` on Window PCs, we want to fast-forward the video by 30 seconds and rewind it by 10 seconds.
To do this, we must first establish a reference to the React player component in order to determine the video's current time.
First, import the `useRef` hook and create the reference for the react-player component.
```
import { useState, useRef } from 'react';
//refer to the react-player component
const playerRef = useRef(null);
//And lastly, add the reference to the react-player component by updating it with this code
<ReactPlayer width={'100%'} height='100%'
url="http://commondatastorage.googleapis.com/gtv-videos-bucket/sample/TearsOfSteel.mp4"
ref={playerRef} //here is the reference
playing={playing}
muted={mute}
/>
```
Let's make the rewinding function, which we'll call handleRewind(). Note that we have access to several instance methods through the react-player library, and we utilize the 'playerRef' to call instance methods on the player. We would utilize the two instance methods "seekTo(amount, type)" and "getCurrentTime()" for the rewind and fast-forward operations.

```
//function to handle rewinding
const handleRewind = () => {
playerRef.current.seekTo(playerRef.current.getCurrentTime() - 10, ‘seconds’)
}
```
```
//function to handle fast-forward
//The major difference between this function and that of rewinding is the addition of 30 seconds to the current time of the video.
const handleFastForward = () => {
playerRef.current.seekTo(playerRef.current.getCurrentTime() + 30, 'seconds')
}
```
The ``ControlIcons component will then get the functions as a prop, which will be destructured inside the component before the icons are activated.
For that do this:
```
//Pass the functions as props
<ControlIcons
playandpause={handlePlayAndPause}
playing={playing}
rewind={handleRewind}
fastForward={handleFastForward}
/>
```
```
//Destructure the props inside the ControlIcons.jsx file
const ControlIcons = ({ playandpause, playing, rewind, fastForward }) => { }
```
```
//Next, call the onClick event on the rewind and fast-forward icons passing the rewind and fastForward props to the event.
//The rewind icon
<IconButton className='controls__icons' aria-label='reqind' onClick={rewind}>
<FastRewind fontSize='large' style={{color:'white'}}/>
</IconButton>
//The fast-forward icon
<IconButton className='controls__icons' aria-label='reqind' onClick={fastForward}>
<FastForwardSharp fontSize='large' style={{color:'white'}}/>
</IconButton>
```
With the code above, we can now rewind and fast-forward the video. You'll note that the video player slider doesn't move to the current time when we rewind or fast-forward the video. As we enter the third section of the controls, let's change that.
The seek bar on the video player is our next goal (the slider). In addition to implementing the ability to click (seek) a specific time on the seek bar and be able to view the video from that moment, we also want it to move as we fast-forward or rewind.
For that, we have to use a callback prop react-player provides - `onProgress`.

Add the value `handlePlayerProgress` (which is a function we would later write) to the list of callback props for the react-player component. Use the following code to update your React player component:
```
<ReactPlayer width={'100%'} height='100%'
url="http://commondatastorage.googleapis.com/gtv-videos-bucket/sample/TearsOfSteel.mp4"
ref={playerRef}
playing={playing}
muted={mute}
onProgress={handlePlayerProgress}
/>
```
The function `handlePlayerProgress` accepts a state argument. An object with other states embedded is represented by this state argument. Some sets of states are changed, and new values are returned, whenever the video player seek bar (the player's timing) changes. In this function, we retain the values of both the previously altered States and the attributes of our `playerstate`. We want this to happen only when the seeking property value in our `playerstate` is negated, which is another reasoning that has been implemented.
You can observe the changing states in your console if you console log the newly altered state. We simply require the `played` state, which is the duration of the video in seconds. This is the `played` property in our `playerstate`.
```
const handlePlayerProgress = (state) => {
console.log('onProgress', state);
if (!playerstate.seeking) {
setPlayerState({...playerstate, ...state})
}
console.log('afterProgress', state);
}
```
Next, pass `played` as a prop to the ControlIcons component:
```
<ControlIcons
playandpause={handlePlayAndPause}
playing={playing}
rewind={handleRewind}
fastForward={handleFastForward}
played={played}
/>
```
Navigate to the ControlIcons.jsx file and destructure the `played` prop in it.
```
const ControlIcons = ({ playandpause, playing, rewind, fastForward, played }) => { }
```
Update your prettoslider with the code below:
```
<PrettoSlider
min={0}
max={100}
value={played*100}
/>
```
Since our slider has a maximum value of 100, we simply removed the `defaultValue` and introduced the `value` prop, which uses the played prop as its value but multiplies it by 100. As the video plays and when we fast-forward or rewind, the seek bar on the video player now moves.
We have to create functions that enable us to seek a particular time using the video player slider. To do this, we would create two functions: `handlePlayerSeek()` and `handlePlayerMouseSeekUp()`.
The `handlePlayerSeek()` is the function triggered when we seek a particular time of the video (when the slider value changes), while the `handlePlayerMouseSeekUp()` is the function triggered when the `mouseup` event is activated.
```
const handlePlayerSeek = (newValue) => {
setPlayerState({...playerstate, played: parseFloat(newValue/100)});]
playerRef.current.seekTo(parseFloat(newValue / 100));
}
const handlePlayerMouseSeekUp = (newValue) => {
setPlayerState({...playerstate, seeking: false});
playerRef.current.seekTo(newValue / 100);
}
```
For the `handlePlayerSeek()`, whenever the slider moves, automatically we get a new value - this is the parameter passed to the function. We then updated the `playerState` by keeping all we have in the state and updating only the `played` value to the `newValue` (which is a string) divided by 100, since `played` only takes a value between 0 and 1.
And for the `handlePlayerMouseSeekUp()`, we passed the parameter of `newValue`, then we updated the `playerState` by keeping all we have in the state and updating only the `seeking` value to `false`. After which we set the video's current time to the time sought.
Now, we have to pass the functions as props to the `ControlIcons` component by updating your `ControlIcons` component.
```
<ControlIcons
playandpause={handlePlayAndPause}
playing={playing}
rewind={handleRewind}
fastForward={handleFastForward}
played={played}
onSeek={handlePlayerSeek}
onSeekMouseUp={handlePlayerMouseSeekUp}
/>
```
Next, destructure the newly added props in your `ControlIcons.jsx` file:
```
const ControlIcons = ({ playandpause, playing, rewind, fastForward, played, onSeek, onSeekMouseUp }) => { }
```
In order to use the `onSeek` and `onSeekMouseUp` props, we need `onChange` and `onChangeCommitted` props, respectively.
The snapshot below explains what these props do.

Now, update your `prettoslider` component with the code below:
```
<PrettoSlider
min={0}
max={100}
value={played*100}
onChange={onSeek}
onChangeCommitted={onSeekMouseUp}
/>
```
We can now seek a particular video time.
Next, right below the video player slider, we want to display the total played time and length of the video.
To do this, we need two instance methods react-player provides for us. The methods are: `getCurrentTime()` and `getDuration()`. The snapshot below explains their use.

```
const currentPlayerTime = playerRef.current ? playerRef.current.getCurrentTime() : '00:00';
const movieDuration = playerRef.current ? playerRef.current.getDuration() : '00:00';
```
The timing values were displayed using ternary operators in the aforementioned code sample. However, we still need to format the time that is returned to our preferences. For this, a globally scoped function is required.
Here is the globally scoped function (I defined it at the top, outside the App component function):
```
const format = (seconds) => {
if (isNaN(seconds)) {
return '00:00'
}
const date = new Date(seconds * 1000);
const hh = date.getUTCHours();
const mm = date.getUTCMinutes();
const ss = date.getUTCSeconds().toString().padStart(2, "0");
if (hh) {
return `${hh}:${mm.toString().padStart(2, "0")}:${ss}`
} else {
return `${mm}:${ss}`
}
};
```
Call the function doing this:
```
const currentPlayerTime = playerRef.current ? playerRef.current.getCurrentTime() : '00:00';
const movieDuration = playerRef.current ? playerRef.current.getDuration() : '00:00';
const playedTime = format(currentPlayerTime);
const fullMovieTime = format(movieDuration);
```
Pass `playedTime` and `fullMovieTime` as props to the `ControlIcons` component. Update the component call using the code below:
```
<ControlIcons
playandpause={handlePlayAndPause}
playing={playing}
rewind={handleRewind}
fastForward={handleFastForward}
played={played}
onSeek={handlePlayerSeek}
onSeekMouseUp={handlePlayerMouseSeekUp}
playedTime={playedTime}
fullMovieTime={fullMovieTime}
/>
```
As usual, destructure them inside the `ControlIcons.jsx` file:
```
const ControlIcons = ({ playandpause, playing, rewind, fastForward, played, onSeek, onSeekMouseUp, playedTime, fullMovieTime }) => { }
```
Finally, update the timing default value after the `prettoslider` component with this:
```
<Typography variant='h7' style={{color:'white'}}>{playedTime}</Typography>
<Typography variant='h7' style={{color:'white'}}>{fullMovieTime}</Typography>
```
You can now view the played time and the movie duration live as the video plays.
And finally, let’s work on the last category of the third segment. Let’s start with the volume icon.
When we click the icon, we should be able to switch between muted and volume up. In that case, depending on the current volume level, the icon should either change to the muted or volume-up icon.
Navigate into the App.js file, where we’ll create the function that handles the muting state.
```
const handleMuting = () => {
setPlayerState({...playerstate, muted: !playerstate.muted})
}
```
For the `handleMuting()`, we kept all that is in the `playerstate` and updated the muted property to an inverse value of the current state of muted. Pass the function and muted property as props into the `ControlIcons` component by modifying your code with this:
```
<ControlIcons
playandpause={handlePlayAndPause}
playing={playing}
rewind={handleRewind}
fastForward={handleFastForward}
played={played}
onSeek={handlePlayerSeek}
onSeekMouseUp={handlePlayerMouseSeekUp}
playedTime={playedTime}
fullMovieTime={fullMovieTime}
muting={handleMuting}
muted={mute
/>
```
Destructure the `muting` and `muted` props in the `ControlIcons.jsx` file:
```
const ControlIcons = ({ playandpause, playing, rewind, fastForward, played, onSeek, onSeekMouseUp, playedTime, fullMovieTime,muting, muted }) => { }
```
Next, import the icon that symbolizes volume off or muted.
```
import { VolumeOff } from '@mui/icons-material';
```
Now, update the whole wrapper for the volume icon with the code below:
```
<IconButton className='controls__icons' aria-label='reqind' onClick={muting}>
{
muted ? (
<VolumeOff fontSize='large' style={{color:'white'}}/>
) : (
<VolumeUp fontSize='large' style={{color:'white'}}/>
)
}
</IconButton>
```
We conditionally rendered two icons in the aforementioned `IconButton wrapper` based on the value of the "muted" attribute in the "playerstate". Additionally, we added an `onClick()` event to the wrapper and specified the value to be `muting`.
For the volume slider just like the `PrettoSlider`, we have two functions to define, one for the `onChange` event and the other for the `onChangeCommitted` event.
```
//function for the `onChange` event
const handleVolumeChange = (e, newValue) => {
setPlayerState({...playerstate, volume:parseFloat(newValue/100), mute:newValue === 0 ? true : false, });
}
//function for the `onChangeCommitted` event
const handleVolumeSeek = (e, newValue) => {
setPlayerState({...playerstate, volume:parseFloat(newValue/100), mute:newValue === 0 ? true : false, });
}
```
For the functions above, we simply updated the `playerstate` by keeping all that is in it and the values of the volume and mute properties.
We then pass the function as props:
```
<ControlIcons
playandpause={handlePlayAndPause}
playing={playing}
rewind={handleRewind}
fastForward={handleFastForward}
played={played}
onSeek={handlePlayerSeek}
onSeekMouseUp={handlePlayerMouseSeekUp}
playedTime={playedTime}
fullMovieTime={fullMovieTime}
muting={handleMuting}
muted={mute}
volume={volume}
volumeChange={handleVolumeChange}
volumeSeek={handleVolumeSeek}
/>
```
Next, we update the volume slider by adding two new props: the `onChange` event and the `onChangeCommitted` event, and passing the `volumeChange` and `volumeSeek` props as their value respectively. Also, we update the default value which defines the volume pitch with the code below:
```
<Typography style={{color:'#fff', paddingTop:'5px'}}>{volume * 100}</Typography>
<Slider
min={0}
max={100}
value={volume * 100}
className='volume__slider'
onChange={volumeChange}
onChangeCommitted={volumeSeek}
/>
```
Regarding the playback speed/rate. Here, we want to include a function that lets us change the speed at which the video plays.
In this section, we’ll work with the `playbackRate` props react-player provides.

We have to create the function that handles the playback rate. This function updates the `playerstate` by simply keeping all that is in it, and updating only `playerbackRate` value to its parameter (rate).
Here is the function:
```
const handlePlayerRate = (rate) => {
setPlayerState({...playerstate, playerbackRate: rate});
}
```
We have to pass this `playerbackRate` property from the `playerstate` as the value for the `playbackRate` prop that we’ll add to the `React Player` component.
According to the statement above, your `React Player` component should have the following props.
```
<ReactPlayer width={'100%'} height='100%'
url="http://commondatastorage.googleapis.com/gtv-videos-bucket/sample/TearsOfSteel.mp4"
ref={playerRef}
playing={playing}
muted={mute}
onProgress={handlePlayerProgress}
playbackRate={playerbackRate}
/>
```
In the code snippet above, we added the `playbackRate` prop to the `React Player` component.
Next, we have to pass both `playerbackRate` property from the `playerstate` and `handlePlayerRate()` as props to `ControlIcons` component.
```
<ControlIcons
playandpause={handlePlayAndPause}
playing={playing}
rewind={handleRewind}
fastForward={handleFastForward}
played={played}
onSeek={handlePlayerSeek}
onSeekMouseUp={handlePlayerMouseSeekUp}
playedTime={playedTime}
fullMovieTime={fullMovieTime}
muting={handleMuting}
muted={mute}
volume={volume}
volumeChange={handleVolumeChange}
volumeSeek={handleVolumeSeek}
playerbackRate={playerbackRate}
playRate={handlePlayerRate}
/>
```
Let’s move into the `ControlIcons.jsx` file where we’ll create the popover and also make use of the recently passed props.
The first thing we have to do here is to set up the `Popover` where we’ll embed the various speed rates.
Import Popover component from Material UI.
```
import Popover from '@mui/material/Popover';
```
Next, destructure the props inside the `ControlIcons.jsx` file:
```
const ControlIcons = ({ playandpause, playing, rewind, fastForward, played, onSeek, onSeekMouseUp, playedTime, fullMovieTime, muting, muted, volume, volumeChange, volumeSeek , playRate, playerbackRate}) => { }
```
The Popover component gotten from MUI comes with a series of things: a state, two functions that trigger the opening and closing of the popover, and two variables. You can find them in the code snippet below:
```
const [anchorEl, setAnchorEl] = React.useState(null);
const handlePopOver = (event) => {
setAnchorEl(event.currentTarget);
};
const handleClose = () => {
setAnchorEl(null);
};
const open = Boolean(anchorEl);
const id = open ? 'playbackrate-popover' : undefined;
```
Let’s systematically activate the popover starting with the displayed value of `1x` in the Video player.
The `Button Wrappper` which displays the current playback rate of the video should be updated with the code below:
```
<Button variant='text' onClick={handlePopOver} className='bottom__icons'>
<Typography>{playerbackRate}X</Typography>
</Button>
```
For the code above, we added the `onClick` event to the `Button Wrapper` which we passed the `handlePopOver()` as its value. After that, we updated the current value of the playback rate using the `playbackRate` prop.
Immediately after the code above, add this:
```
<Popover
id={id}
open={open}
anchorEl={anchorEl}
onClose={handleClose}
anchorOrigin={{
vertical: 'top',
horizontal: 'center',
}}
transformOrigin={{
vertical: 'bottom',
horizontal: 'center
}}>
<Grid container direction='column-reverse'>
{
[0.5,1,1.5,2].map((rate) => (
<Button variant='text' onClick={() => playRate(rate)}>
<Typography color={rate === playerbackRate ? 'secondary' : 'default'}>{rate}</Typography>
</Button>
))
}
</Grid>
</Popover>
```
In the code above, we simply passed `id`, `open`, `anchorE!`, and `onClose` as props to the `Popover` wrapper. We also structured the position of the `Popover` using the `anchorOrigin` and `transformOrigin`. Finally, inside the `Popover` wrapper, we mapped the values which symbolize the playback rate, then they are passed as the argument to the `playRate`.
Now, we can toggle through four values for our playback rate. By default, the `playback rate` is set to 1.
Navigate to the `App.js`.
Finally, let’s implement the `Full-Screen Mode`. Remember that we imported `screenfull` from the screenfull package.
We need to make a reference to the wrapper for the whole video player i.e the div with a className of `playerDiv`.
Create the reference using `useRef`:
```
const playerDivRef = useRef(null);
```
Call the reference on the Video player wrapper:
```
<div className='playerDiv' ref={playerDivRef}></div>
```
Create the function that triggers full-screen mode when we toggle the icon.
```
const handleFullScreenMode = () => {
screenfull.toggle(playerDivRef.current);
}
```
Pass the function as a prop to the `ControlIcons` component. Update the `ControlIcons` component with this:
```
<ControlIcons
playandpause={handlePlayAndPause}
playing={playing}
rewind={handleRewind}
fastForward={handleFastForward}
played={played}
onSeek={handlePlayerSeek}
onSeekMouseUp={handlePlayerMouseSeekUp}
playedTime={playedTime}
fullMovieTime={fullMovieTime}
muting={handleMuting}
muted={mute}
volume={volume}
volumeChange={handleVolumeChange}
volumeSeek={handleVolumeSeek}
playerbackRate={playerbackRate}
playRate={handlePlayerRate}
fullScreenMode={handleFullScreenMode}
/>
```
Destructure the `fullScreenMode` prop inside the `ControlIcons.jsx` file.
```
const ControlIcons = ({ playandpause, playing, rewind, fastForward, played, onSeek, onSeekMouseUp, playedTime, fullMovieTime, muting, muted, volume, volumeChange, volumeSeek , playRate, playerbackRate, fullScreenMode}) => { }
```
Add the `onClick` event (with the `fullScreenMode` prop as its value) to the `IconButton` wrapper where the full-screen icon is embedded.
```
<IconButton className='bottom__icons' onClick={fullScreenMode}>
<Fullscreen fontSize='large'/>
</IconButton>
```
The result for this build should look like this:
## Conclusion
In this article, we learned how to build and customize a video player using the [react-player package](https://www.npmjs.com/package/react-player) and [Mui](https://mui.com/) to style and import the needed icons. However, you can always take the build further by adding some unique features to make the player suit your taste completely.
Here is the [source code](https://github.com/shegz101/Video-player) and the [deployed version](https://video-player-theta-seven.vercel.app/).
If you have any questions, do well to use the comment section below.