useVideo
React hook to manage a video.
Add the hook via the CLI:
npx @novajslabs/cli add useVideo
npx @novajslabs/cli add useVideo
pnpm dlx @novajslabs/cli add useVideo
Or copy and paste the code into your project:
import { useEffect, useState, RefObject } from "react";
export const useVideo = (ref: RefObject<HTMLVideoElement>) => {
const video = ref.current;
const [videoState, setVideoState] = useState({
isPaused: video ? video?.paused : true,
isMuted: video ? video?.muted : false,
currentVolume: video ? video?.volume : 100,
currentTime: video ? video?.currentTime : 0,
});
const play = () => {
video?.play();
setVideoState((prev) => {
return {
...prev,
isPaused: false,
isMuted: video ? video.muted : prev.isMuted,
};
});
};
const pause = () => {
video?.pause();
setVideoState((prev) => {
return {
...prev,
isPaused: true,
};
});
};
const handlePlayPauseControl = (e: Event) => {
setVideoState((prev) => {
return {
...prev,
isPaused: (e.target as HTMLVideoElement).paused,
};
});
};
const togglePause = () => (video?.paused ? play() : pause());
const handleVolume = (delta: number) => {
const deltaDecimal = delta / 100;
if (video) {
let newVolume = video?.volume + deltaDecimal;
if (newVolume >= 1) {
newVolume = 1;
} else if (newVolume <= 0) {
newVolume = 0;
}
video.volume = newVolume;
setVideoState((prev) => {
return {
...prev,
currentVolume: newVolume * 100,
};
});
}
};
const handleVolumeControl = (e: Event) => {
if (e.target && video) {
const newVolume = (e.target as HTMLVideoElement).volume * 100;
if (newVolume === videoState.currentVolume) {
handleMute(video.muted);
return;
}
setVideoState((prev) => ({
...prev,
currentVolume: (e.target as HTMLVideoElement).volume * 100,
}));
}
};
const handleMute = (mute: boolean) => {
if (video) {
video.muted = mute;
setVideoState((prev) => {
return {
...prev,
isMuted: mute,
};
});
}
};
const handleTime = (delta: number = 5) => {
if (video) {
let newTime = video.currentTime + delta;
if (newTime >= video.duration) {
newTime = video.duration;
} else if (newTime <= 0) {
newTime = 0;
}
video.currentTime = newTime;
setVideoState((prev) => {
return {
...prev,
currentTime: newTime,
};
});
}
};
const handleTimeControl = (e: Event) => {
setVideoState((prev) => {
return {
...prev,
currentTime: (e.target as HTMLVideoElement).currentTime,
};
});
};
const toggleFullscreen = () => {
if (!document.fullscreenElement) {
video?.requestFullscreen().catch((err) => {
console.log(err);
});
} else {
document.exitFullscreen();
}
};
useEffect(() => {
return () => {
pause();
};
}, []);
useEffect(() => {
if (video) {
video.addEventListener("volumechange", handleVolumeControl);
video.addEventListener("play", handlePlayPauseControl);
video.addEventListener("pause", handlePlayPauseControl);
video.addEventListener("timeupdate", handleTimeControl);
return () => {
video.removeEventListener("volumechange", handleVolumeControl);
video.removeEventListener("play", handlePlayPauseControl);
video.removeEventListener("pause", handlePlayPauseControl);
video.removeEventListener("timeupdate", handleTimeControl);
};
}
}, [video]);
return {
...videoState,
play,
pause,
togglePause,
increaseVolume: (increase: number = 5) => handleVolume(increase),
decreaseVolume: (decrease: number = 5) => handleVolume(decrease * -1),
mute: () => handleMute(true),
unmute: () => handleMute(false),
toggleMute: () => handleMute(!video?.muted),
forward: (increase: number = 5) => handleTime(increase),
back: (decrease: number = 5) => handleTime(decrease * -1),
toggleFullscreen,
};
};
import { useEffect, useState } from "react";
export const useVideo = (ref) => {
const video = ref.current;
const [videoState, setVideoState] = useState({
isPaused: video ? video?.paused : true,
isMuted: video ? video?.muted : false,
currentVolume: video ? video?.volume : 100,
currentTime: video ? video?.currentTime : 0,
});
const play = () => {
video?.play();
setVideoState((prev) => {
return {
...prev,
isPaused: false,
isMuted: video ? video.muted : prev.isMuted,
};
});
};
const pause = () => {
video?.pause();
setVideoState((prev) => {
return {
...prev,
isPaused: true,
};
});
};
const handlePlayPauseControl = (e) => {
setVideoState((prev) => {
return {
...prev,
isPaused: e.target.paused,
};
});
};
const togglePause = () => (video?.paused ? play() : pause());
const handleVolume = (delta) => {
const deltaDecimal = delta / 100;
if (video) {
let newVolume = video?.volume + deltaDecimal;
if (newVolume >= 1) {
newVolume = 1;
} else if (newVolume <= 0) {
newVolume = 0;
}
video.volume = newVolume;
setVideoState((prev) => {
return {
...prev,
currentVolume: newVolume * 100,
};
});
}
};
const handleVolumeControl = (e) => {
if (e.target && video) {
const newVolume = e.target.volume * 100;
if (newVolume === videoState.currentVolume) {
handleMute(video.muted);
return;
}
setVideoState((prev) => ({
...prev,
currentVolume: e.target.volume * 100,
}));
}
};
const handleMute = (mute) => {
if (video) {
video.muted = mute;
setVideoState((prev) => {
return {
...prev,
isMuted: mute,
};
});
}
};
const handleTime = (delta = 5) => {
if (video) {
let newTime = video.currentTime + delta;
if (newTime >= video.duration) {
newTime = video.duration;
} else if (newTime <= 0) {
newTime = 0;
}
video.currentTime = newTime;
setVideoState((prev) => {
return {
...prev,
currentTime: newTime,
};
});
}
};
const handleTimeControl = (e) => {
setVideoState((prev) => {
return {
...prev,
currentTime: e.target.currentTime,
};
});
};
const toggleFullscreen = () => {
if (!document.fullscreenElement) {
video?.requestFullscreen().catch((err) => {
console.log(err);
});
} else {
document.exitFullscreen();
}
};
useEffect(() => {
return () => {
pause();
};
}, []);
useEffect(() => {
if (video) {
video.addEventListener("volumechange", handleVolumeControl);
video.addEventListener("play", handlePlayPauseControl);
video.addEventListener("pause", handlePlayPauseControl);
video.addEventListener("timeupdate", handleTimeControl);
return () => {
video.removeEventListener("volumechange", handleVolumeControl);
video.removeEventListener("play", handlePlayPauseControl);
video.removeEventListener("pause", handlePlayPauseControl);
video.removeEventListener("timeupdate", handleTimeControl);
};
}
}, [video]);
return {
...videoState,
play,
pause,
togglePause,
increaseVolume: (increase = 5) => handleVolume(increase),
decreaseVolume: (decrease = 5) => handleVolume(decrease * -1),
mute: () => handleMute(true),
unmute: () => handleMute(false),
toggleMute: () => handleMute(!video?.muted),
forward: (increase = 5) => handleTime(increase),
back: (decrease = 5) => handleTime(decrease * -1),
toggleFullscreen,
};
};
Requirements
React 16.8 or higher
Parameters
ref required
Type: HTMLVideoElement
The HTML video element to be managed.
Return values
isPaused
Type: boolean
Indicates whether the video is currently paused (true
) or playing (false
).
isMuted
Type: boolean
Indicates whether the video is currently muted (true
) or unmuted (false
).
currentVolume
Type: number
The current volume level of the video, ranging from 0 to 100.
currentTime
Type: number
The current playback position (in seconds) of the video.
play
Type: function
Play the video.
pause
Type: function
Pause the video.
togglePause
Type: function
Toggle between playing and pausing the video.
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 video.
unmute
Type: function
Unmute the video.
toggleMute
Type: function
Toggle between muting and unmuting the video.
forward
Type: function
Fast forward the video by a specified number of seconds.
back
Type: function
Rewind the video by a specified number of seconds.
toggleFullscreen
Type: function
Toggle fullscreen mode of the video.
Example
import { useRef } from "react";
import { useVideo } from "./hooks/useVideo";
const App = () => {
const videoRef = useRef<HTMLVideoElement>(null);
const {
isPaused,
isMuted,
currentVolume,
currentTime,
play,
pause,
togglePause,
increaseVolume,
decreaseVolume,
mute,
unmute,
toggleMute,
forward,
back,
toggleFullscreen,
} = useVideo(videoRef);
return (
<div>
<video ref={videoRef} src="video.mp4" />
<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>
<button onClick={toggleFullscreen}>Fullscreen</button>
</div>
</div>
);
};
export default App;
Use cases
Here are some use cases where this React hook is useful:
- Implementing custom video controls
- Building a multimedia learning platform with interactive video playback controls
- Creating a video carousel component with autoplay functionality
- Developing a video editing tool with precise seeking and playback controls