如何在 React 中创建视频播放器
让我们开始编码
最近我最感兴趣的事情之一是创建一个完全定制的视频播放器。显然,现在我们有一些提供小部件的服务可以在我们的网站上使用。
或者,另一方面,您已经拥有可以安装并开始使用的依赖项。但这些功能是有代价的,在这种情况下,就是缺乏定制或难以定制。
这就是我有创建自己的视频播放器的想法的原因,显然它并不像我想象的那么难,而且最终我发现它很有趣。
正是由于这个原因,我才有了写这篇文章的想法,逐步解释如何制作一个简单的视频播放器,但用同样的逻辑,你可以走得更远。
在今天的例子中,我们将使用这个视频,它有声音并且完全免费。
让我们开始编码
今天我们不会使用任何外部依赖项,因此您将完全熟悉所有内容。
关于样式,最后我会给出 CSS 代码,这是因为本文的重点是讲授视频播放器工作背后的逻辑。
我要求你做的第一件事是下载上面提到的视频,然后将文件重命名为video.mp4
。最后在你的项目中创建一个名为的文件夹assets
,并将文件拖到该文件夹中。
为了避免将代码放在单个文件中,我们需要创建自己的钩子来负责控制视频播放器的整个操作。
// @src/hooks/useVideoPlayer.js
const useVideoPlayer = () => {
// ...
};
export default useVideoPlayer;
在我们的钩子中,我们将只使用两个 React 钩子,useState()
和useEffect()
。
// @src/hooks/useVideoPlayer.js
import { useState, useEffect } from "react";
const useVideoPlayer = () => {
// ...
};
export default useVideoPlayer;
现在我们可以开始创建状态,我们称之为playerState。这个状态有四个属性:isPlaying、isMuted、progress 和 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;
我希望你记住的一件事是,我们的钩子必须采用一个参数,在这种情况下,它将是我们视频的引用,我们将其命名为 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;
现在我们可以创建一个函数来判断播放器是否暂停。为此,我们将保留 playerState 中所有其他属性的值,并且每次执行该函数时,都会返回 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;
现在我们需要useEffect()
通过 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,
});
};
useEffect(() => {
playerState.isPlaying
? videoElement.current.play()
: videoElement.current.pause();
}, [playerState.isPlaying, videoElement]);
// ...
};
export default useVideoPlayer;
现在我们必须创建一个函数来帮助我们了解视频的进度,即根据视频的时长,我们希望进度条显示我们已经看了多少视频。
为此,我们将创建一个名为 的函数handleOnTimeUpdate()
,用于计算视频已观看量以及剩余观看量。之后,我们将保留状态中所有其他属性的值,并仅更新进度值。
// @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;
我们想要实现的功能之一是可以拖动进度条,这样我们就可以选择在哪里观看视频。
这样,我们将创建一个名为的函数handleVideoProgress()
,它将有一个参数,在本例中就是事件。
然后,我们将事件值从字符串转换为数字。这是因为我们想直接告诉 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,
});
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;
我们想要实现的另一个功能是视频播放速度,因为我相信并不是每个人都是 1.0 倍速的粉丝,而且有些人会以 1.25 倍速观看视频。
为此,我们将创建一个名为的函数handleVideoSpeed()
,该函数将接收一个事件作为单个参数,然后将该事件的值转换为数字,最后我们将告诉 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,
});
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;
我想要添加的最后一个功能是视频静音和取消静音的功能。其计算逻辑与播放/暂停非常相似。
// @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;
最后,只需返回我们的状态和所有创建的函数。
// @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;
现在我们可以开始处理我们的App.jsx
组件了,记录一下,使用的图标库是Boxicons,字体是DM Sans。
首先,我将给出我们的 css 代码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;
}
现在我们可以开始处理我们的组件,为此我们将导入我们需要的一切,在这种情况下,它是我们的样式、我们的视频和我们的钩子。
// @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;
然后我们导入useRef()
钩子来创建 videoElement 的引用。像这样:
// @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;
然后,我们可以从钩子中获取 playerState 和每个函数。像这样:
// @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;
现在我们终于可以开始处理我们的模板了,这样我们将开始处理我们的视频元素,它将具有三个道具,来源将是我们的视频,我们仍然将传递我们的参考和我们的handleOnTimeUpdate()
功能。
// @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;
现在我们可以开始处理视频控件了,先从播放和暂停按钮开始。我们将向其传递函数togglePlay()
并进行条件渲染,以便根据 isPlaying 属性的值显示相应的图标。
// @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;
现在我们可以开始处理输入了,它将是 range 类型,最小值为 0,最大值为 100。同样,我们将传递handleVideoProgress()
函数和 progress 属性的值。
// @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;
现在我们将处理用于选择视频播放速度的元素。我们将向其传递 speed 属性和handleVideoSpeed()
函数的值。
// @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;
最后,同样重要的是,我们将添加一个按钮,用于控制视频的静音和取消静音。我们将向该按钮传递一个toggleMute()
函数,并根据 isMuted 属性进行条件渲染,以显示相应的图标。
// @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;
最终结果应该如下所示:
结论
一如既往,希望你觉得这篇文章有趣。如果你发现本文有任何错误,请在评论区指出。🥳
祝你度过美好的一天!🙌
文章来源:https://dev.to/franciscomendes10866/how-to-create-a-video-player-in-react-40jj