React ๐ฝ๏ธ์์ ๋ง์ถคํ ๋น๋์ค ํ๋ ์ด์ด ๋ง๋ค๊ธฐ
์ค์
์๋ก์ด ๋ฐ์ ์ฑ ๋ง๋ค๊ธฐ
npx create-react-app custom-video-player
๋์ฒญ์
App.js
์ ์ฑ div์์ ๋ชจ๋ ํญ๋ชฉ์ ์ญ์ ํฉ๋๋ค.import "./App.css";
function App() {
return <div className="app"></div>;
}
export default App;
App.css
์ ๋ชจ๋ ํญ๋ชฉ ์ญ์ index.css
์ถ๊ฐ-* {
margin: 0;
}
๋น๋์ค ํ๋ ์ด์ด์ฉ UI ๋ง๋ค๊ธฐ
๋น๋์ค ์ถ๊ฐ
์ฑ div ๋ด์์ ๋น๋์ค์ src์ ํจ๊ป ๋น๋์ค ํ๊ทธ๋ฅผ ์ถ๊ฐํ๊ณ ์คํ์ผ ์ง์ ์ ์ํด className๋ ์ถ๊ฐํ๊ฒ ์ต๋๋ค.
<video
className="video"
src="https://res.cloudinary.com/dssvrf9oz/video/upload/v1635662987/pexels-pavel-danilyuk-5359634_1_gmixla.mp4"
></video>
๋น๋์ค ์ปจํธ๋กค ์ถ๊ฐ
๋น๋์ค ๊ตฌ์ฑ ์์ ์๋์ ์์ด์ฝ์ผ๋ก ์ผ๋ถ Svgs๊ฐ ์๋ ์ด div๋ฅผ ์ถ๊ฐํฉ๋๋ค. ๋์ ๊ฐ์ ์ง์ Svg๋ฅผ ์ฌ์ฉํ๊ฑฐ๋ ์์ด์ฝ์ ๋ํ ์์ด์ฝ ๋ผ์ด๋ธ๋ฌ๋ฆฌ๋ฅผ ์ฌ์ฉํ ์ ์์ต๋๋ค :).
<div className="controlsContainer">
<div className="controls">
<img className="controlsIcon" alt="" src="/backward-5.svg" />
<img className="controlsIcon--small" alt="" src="/play.svg" />
<img className="controlsIcon" alt="" src="/forward-5.svg" />
</div>
</div>
์๊ฐ์ ๋ํ ์งํ๋ฅ ํ์์ค ์ถ๊ฐ
๋ํ ํ์ฌ ์๊ฐ๊ณผ ๋น๋์ค์ ์ด ์๊ฐ์ ๋ณด์ฌ์ฃผ๋ ์งํ๋ฅ ํ์์ค์ ๋ง๋ค ๊ฒ์ ๋๋ค.
<div className="timecontrols">
<p className="controlsTime">1:02</p>
<div className="time_progressbarContainer">
<div style={{ width: "40%" }} className="time_progressBar"></div>
</div>
<p className="controlsTime">2:05</p>
</div>
Forgive me for not having very good naming conventions in classes. I have forgotten how to name my classes because of Tailwind :P
UI ์คํ์ผ๋ง
๋น๋์ค ํ๋ ์ด์ด๋ ์ง๊ธ ๋ณด๊ธฐ์ ๋งค์ฐ ๋ชป์๊ฒผ์ผ๋ ์คํ์ผ์ ์ง์ ํด ๋ณด๊ฒ ์ต๋๋ค. App.css์์ ๋ช ๊ฐ์ง ์คํ์ผ์ ์ถ๊ฐํ๊ฒ ์ต๋๋ค.
/* Main Container */
.app {
display: flex;
flex-direction: column;
width: 100vw;
height: 100vh;
overflow: hidden;
}
/* Video */
.video {
width: 100vw;
height: 100vh;
}
/* Controls */
.controlsContainer {
display: flex;
flex-direction: column;
align-items: center;
width: 100vw;
background-color: transparent;
margin-top: -50vw;
padding: 0 40px;
z-index: 20;
}
.controls {
display: flex;
align-items: center;
justify-content: space-evenly;
padding-top: 18rem;
margin: auto;
}
.controlsIcon {
width: 40px;
height: 40px;
cursor: pointer;
margin-left: 10rem;
margin-right: 10rem;
}
.controlsIcon--small {
width: 32px;
height: 32px;
cursor: pointer;
margin-left: 10rem;
margin-right: 10rem;
}
/* The time controls section */
.timecontrols {
display: flex;
align-items: center;
justify-content: space-evenly;
position: absolute;
bottom: 4rem;
margin-left: 10vw;
}
.time_progressbarContainer {
background-color: gray;
border-radius: 15px;
width: 75vw;
height: 5px;
z-index: 30;
position: relative;
margin: 0 20px;
}
.time_progressBar {
border-radius: 15px;
background-color: indigo;
height: 100%;
}
.controlsTime {
color: white;
}
์ด์ ๋น๋์ค ํ๋ ์ด์ด๋ ๋ค์๊ณผ ๊ฐ์ต๋๋ค.
ํ๋ ์ด์ด์ ๋ ผ๋ฆฌ ์ถ๊ฐ
๊ธฐ๋ฅ์ ์์ ํ๋ ค๋ฉด ๋จผ์ useRef ํํฌ๋ฅผ ์ฌ์ฉํ์ฌ ๋น๋์ค์ ์ฐธ์กฐ๋ฅผ ์ฒจ๋ถํด์ผ ํฉ๋๋ค. ๋ฐ๋ผ์ ์๋ ์ ๊ณต๋ ๋จ๊ณ๋ฅผ ๋ฐ๋ฅด์ญ์์ค.
const videoRef = useRef(null);
import { useRef } from "react";
<video
ref={videoRef}
className="video"
src="https://res.cloudinary.com/dssvrf9oz/video/upload/v1635662987/pexels-pavel-danilyuk-5359634_1_gmixla.mp4"
></video>
์ฌ์ ๋ฐ ์ผ์ ์ ์ง ๊ธฐ๋ฅ
์ฌ์ ๋ฐ ์ผ์ ์ ์ง๋ฅผ ์ํด ์ ์ด ์ธ์๋ฅผ ์ทจํ๊ณ ์ ์ด์ ๋ฐ๋ผ ๋น๋์ค๋ฅผ ์ฌ์ํ๊ฑฐ๋ ์ผ์ ์ค์งํ๋ ๊ฐ๋จํ ํจ์๋ฅผ ๋ง๋ญ๋๋ค.
const videoHandler = (control) => {
if (control === "play") {
videoRef.current.play();
} else if (control === "pause") {
videoRef.current.pause();
}
};
์ด์ play.svg ์ด๋ฏธ์ง์์ onClick ๊ธฐ๋ฅ์ ์ถ๊ฐํ์ฌ ๋น๋์ค๋ฅผ ์์ํฉ๋๋ค.
<img
onClick={() => videoHandler("play")}
className="controlsIcon--small"
alt=""
src="/play.svg"
/>
์์ด์ฝ์ ํด๋ฆญํ์๋ฉด ์์์ด ์ฌ์๋ฉ๋๋ค!
์ฌ์/์ผ์์ ์ง ์ํ์ ๋ฐ๋ฅธ ์์ด์ฝ ๋ณ๊ฒฝ
์ด๋ฅผ ๋ฌ์ฑํ๊ธฐ ์ํด useState ํํฌ๋ฅผ ์ฌ์ฉํ ๊ฒ์ ๋๋ค. ๋ค์๊ณผ ๊ฐ์ด ์ฌ์ ์ํ๋ฅผ ๋ง๋ญ๋๋ค.
const [playing, setPlaying] = useState(false);
const ๋น๋์ค ํธ๋ค๋ฌ ํจ์์์ onClick ๊ฐ์ ๋ค์๊ณผ ๊ฐ์ด ๋ณ๊ฒฝํด์ผ ํฉ๋๋ค.
const videoHandler = (control) => {
if (control === "play") {
videoRef.current.play();
setPlaying(true);
} else if (control === "pause") {
videoRef.current.pause();
setPlaying(false);
}
};
์์ด์ฝ ๋ณ๊ฒฝ
์ฌ์ ์์ด์ฝ์ด ์๋ ๊ณณ์์ ์ด์ ์ผํญ ์ฐ์ฐ์์ ๋์์ ๋ฐ์ ์กฐ๊ฑด์ ๋ฐ๋ผ ๋ ๋๋งํฉ๋๋ค.
{playing ? (
<img
onClick={() => videoHandler("pause")}
className="controlsIcon--small"
alt=""
src="/pause.svg"
/>
) : (
<img
onClick={() => videoHandler("play")}
className="controlsIcon--small"
alt=""
src="/play.svg"
/>
)}
์ด์ ๋น๋์ค๋ฅผ ์ฌ์ํ๊ณ ์ผ์ ์ค์งํ ์ ์์ต๋๋ค ๐ฅณ
๋น๋์ค ์ ๋ฌ ๋ฐ ๋๋๋ฆฌ๊ธฐ
๋๋ ์ด๊ฒ์ ์ํด ๋งค์ฐ ๊ฐ๋จํ ๊ธฐ๋ฅ์ ๋ง๋ค ๊ฒ์ ๋๋ค.
const fastForward = () => {
videoRef.current.currentTime += 5;
};
const revert = () => {
videoRef.current.currentTime -= 5;
};
์ด์ ์ด๋ฌํ ๊ธฐ๋ฅ์ ๊ฐ ๋ฒํผ์ onClick์ผ๋ก ์ถ๊ฐํ๊ฒ ์ต๋๋ค.
์์ผ๋ก
<img
onClick={fastForward}
className="controlsIcon"
alt=""
src="/forward-5.svg"
/>
๋์๊ฐ๋ ๊ฒ
<img
onClick={revert}
className="controlsIcon"
alt=""
src="/backward-5.svg"
/>
์๊ฐ ์งํ๋ฅ ํ์์ค
๋์์ ๊ธธ์ด ๊ฐ์ ธ์ค๊ธฐ
๋์์ ๊ธธ์ด๋ฅผ ํ์ธํ๋ ค๋ฉด ๋ค์ ๋จ๊ณ๋ฅผ ๋ฐ๋ฅด์ธ์.
<video
id="video1"
ref={videoRef}
className="video"
src="https://res.cloudinary.com/dssvrf9oz/video/upload/v1635662987/pexels-pavel-danilyuk-5359634_1_gmixla.mp4"
></video>
const [videoTime, setVideoTime] = useState(0);
if (control === "play") {
videoRef.current.play();
setPlaying(true);
var vid = document.getElementById("video1");
setVideoTime(vid.duration);
}
์ด์ ํ๋์ฝ๋ฉ๋ ์๊ฐ ๋์ videoTime ๋ณ์๋ฅผ ์ฌ์ฉํ ์ ์์ต๋๋ค. ์ด ๋ฌธ์์ด ์กฐ์์ ์๊ฐ์ 1:05์ ๊ฐ์ ํ์์ผ๋ก ๋ง๋ญ๋๋ค.
<p className="controlsTime">
{Math.floor(videoTime / 60) + ":" + ("0" + Math.floor(videoTime % 60)).slice(-2)}
</p>
๋น๋์ค์ ํ์ฌ ์๊ฐ ๊ฐ์ ธ์ค๊ธฐ
๋น๋์ค์ ํ์ฌ ์๊ฐ์ ์ป์ผ๋ ค๋ฉด 1์ด๋ง๋ค ์คํ๋๋ ํจ์๋ฅผ ์ฌ์ฉํด์ผ ํ๋ฏ๋ก window.setInterval์ ์ฌ์ฉํ ๊ฒ์ ๋๋ค.
window.setInterval(function () {
setCurrentTime(videoRef.current?.currentTime);
}, 1000);
์ด์ ํญ์ ๊ทธ๋ ๋ฏ์ด ๊ฐ์ ์ ์ฅํ ์ํ๋ฅผ ๋ง๋ค์ด์ผ ํฉ๋๋ค.
const [currentTime, setCurrentTime] = useState(0);
ํ๋ ์ฝ๋ ๊ฐ ๋์ ๋ณ์๋ฅผ ์ฌ์ฉํฉ๋๋ค.
<p className="controlsTime">
{Math.floor(currentTime / 60) + ":" + ("0" + Math.floor(currentTime % 60)).slice(-2)}
</p>
์งํ๋ฅ ๊ฐ์ ธ์ค๊ธฐ ๋ฐ ์งํ๋ฅ ํ์์ค์ ์ค์
์งํ์ ์ํ ๋ค๋ฅธ ์ํ ๋ง๋ค๊ธฐ-
const [progress, setProgress] = useState(0);
์ด์ ์ฐ๋ฆฌ๊ฐ ๋ง๋ window.setInterval ํจ์ ์์ ๋ค๋ฅธ ์ค์ ์ถ๊ฐํฉ๋๋ค.
setProgress((videoRef.current?.currentTime / videoTime) * 100);
๊ธฐ๋ฅ์ ์ด์ ๋ค์๊ณผ ๊ฐ์ต๋๋ค.
window.setInterval(function () {
setCurrentTime(videoRef.current?.currentTime);
setProgress((videoRef.current?.currentTime / videoTime) * 100);
}, 1000);
์ด์ ๋ง์ถค ๋์์ ํ๋ ์ด์ด๊ฐ ์ค๋น๋์์ต๋๋ค ๐๐
์ ์ฉํ ๋งํฌ-
GitHub repository
ReactJS docs
All socials
Reference
์ด ๋ฌธ์ ์ ๊ดํ์ฌ(React ๐ฝ๏ธ์์ ๋ง์ถคํ ๋น๋์ค ํ๋ ์ด์ด ๋ง๋ค๊ธฐ), ์ฐ๋ฆฌ๋ ์ด๊ณณ์์ ๋ ๋ง์ ์๋ฃ๋ฅผ ๋ฐ๊ฒฌํ๊ณ ๋งํฌ๋ฅผ ํด๋ฆญํ์ฌ ๋ณด์๋ค https://dev.to/byteslash/create-a-custom-video-player-in-react-4bboํ ์คํธ๋ฅผ ์์ ๋กญ๊ฒ ๊ณต์ ํ๊ฑฐ๋ ๋ณต์ฌํ ์ ์์ต๋๋ค.ํ์ง๋ง ์ด ๋ฌธ์์ URL์ ์ฐธ์กฐ URL๋ก ๋จ๊ฒจ ๋์ญ์์ค.
์ฐ์ํ ๊ฐ๋ฐ์ ์ฝํ ์ธ ๋ฐ๊ฒฌ์ ์ ๋ (Collection and Share based on the CC Protocol.)
์ข์ ์นํ์ด์ง ์ฆ๊ฒจ์ฐพ๊ธฐ
๊ฐ๋ฐ์ ์ฐ์ ์ฌ์ดํธ ์์ง
๊ฐ๋ฐ์๊ฐ ์์์ผ ํ ํ์ ์ฌ์ดํธ 100์ ์ถ์ฒ ์ฐ๋ฆฌ๋ ๋น์ ์ ์ํด 100๊ฐ์ ์์ฃผ ์ฌ์ฉํ๋ ๊ฐ๋ฐ์ ํ์ต ์ฌ์ดํธ๋ฅผ ์ ๋ฆฌํ์ต๋๋ค