[JavaScript30] ๐ŸŽž๏ธ 11. Custom Video Player

์กฐ์ค€ํ˜•ยท2021๋…„ 7์›” 9์ผ
1

JavaScript30

๋ชฉ๋ก ๋ณด๊ธฐ
11/30
post-thumbnail

๐ŸŽž๏ธ 11. Custom Video Player

๋™์˜์ƒ ํ”Œ๋ ˆ์ด์–ด์˜ ๋‹ค์–‘ํ•œ ๊ธฐ๋Šฅ์„ ์ž๋ฐ”์Šคํฌ๋ฆฝํŠธ๋กœ ๊ตฌํ˜„.

์ดˆ๊ธฐ์ฝ”๋“œ

<!DOCTYPE html>
<html lang="ko">
<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>HTML Video Player</title>
    <link rel="stylesheet" href="style_JuneHyung.css">
</head>
<body>
    <div class="player">
        <video class="player__video viewer" src="652333414.mp4"></video>
        <div class="player__controls">
            <div class="progress">
                <div class="progress__filled"></div>
            </div>
            <button class="player__button toggle" title="Toggle Play">โ–บ</button>
            <input type="range" name="volume" class="player__slider" min="0" max="1" step="0.05" value="1">
            <input type="range" name="playbackRate" class="player__slider" min="0.5" max="2" step="0.1" value="1">
            <button data-skip="-10" class="player__button">ยซ 10s</button>
            <button data-skip="25" class="player__button">25s ยป</button>
        </div>
    </div>    
    <script src="scripts_JuneHyung.js"></script>
</body>
</html>
@charset "utf-8";
html {
    box-sizing: border-box;
}

*,
*:before,
*:after {
    box-sizing: inherit;
}

body {
    margin: 0;
    padding: 0;
    display: flex;
    background: #7a419b;
    min-height: 100vh;
    background: linear-gradient(135deg, #7c1599 0%, #921099 48%, #7e4ae8 100%);
    background-size: cover;
    align-items: center;
    justify-content: center;
}

.player {
    max-width: 750px;
    border: 5px solid rgba(0, 0, 0, 0.2);
    box-shadow: 0 0 20px rgba(0, 0, 0, 0.2);
    position: relative;
    font-size: 0;
    overflow: hidden;
}

/* This css is only applied when fullscreen is active. */
.player:fullscreen {
    max-width: none;
    width: 100%;
}

.player:-webkit-full-screen {
    max-width: none;
    width: 100%;
}

.player__video {
    width: 100%;
}

.player__button {
    background: none;
    border: 0;
    line-height: 1;
    color: white;
    text-align: center;
    outline: 0;
    padding: 0;
    cursor: pointer;
    max-width: 50px;
}

.player__button:focus {
    border-color: #ffc600;
}

.player__slider {
    width: 10px;
    height: 30px;
}

.player__controls {
    display: flex;
    position: absolute;
    bottom: 0;
    width: 100%;
    transform: translateY(100%) translateY(-5px);
    transition: all 0.3s;
    flex-wrap: wrap;
    background: rgba(0, 0, 0, 0.1);
}

.player:hover .player__controls {
    transform: translateY(0);
}

.player:hover .progress {
    height: 15px;
}

.player__controls > * {
    flex: 1;
}

.progress {
    flex: 10;
    position: relative;
    display: flex;
    flex-basis: 100%;
    height: 5px;
    transition: height 0.3s;
    background: rgba(0, 0, 0, 0.5);
    cursor: ew-resize;
}

.progress__filled {
    width: 50%;
    background: #ffc600;
    flex: 0;
    flex-basis: 50%;
}

/* unholy css to style input type="range" */

input[type='range'] {
    -webkit-appearance: none;
    background: transparent;
    width: 100%;
    margin: 0 5px;
}

input[type='range']:focus {
    outline: none;
}

input[type='range']::-webkit-slider-runnable-track {
    width: 100%;
    height: 8.4px;
    cursor: pointer;
    box-shadow: 1px 1px 1px rgba(0, 0, 0, 0), 0 0 1px rgba(13, 13, 13, 0);
    background: rgba(255, 255, 255, 0.8);
    border-radius: 1.3px;
    border: 0.2px solid rgba(1, 1, 1, 0);
}

input[type='range']::-webkit-slider-thumb {
    height: 15px;
    width: 15px;
    border-radius: 50px;
    background: #ffc600;
    cursor: pointer;
    -webkit-appearance: none;
    margin-top: -3.5px;
    box-shadow: 0 0 2px rgba(0, 0, 0, 0.2);
}

input[type='range']:focus::-webkit-slider-runnable-track {
    background: #bada55;
}

input[type='range']::-moz-range-track {
    width: 100%;
    height: 8.4px;
    cursor: pointer;
    box-shadow: 1px 1px 1px rgba(0, 0, 0, 0), 0 0 1px rgba(13, 13, 13, 0);
    background: #ffffff;
    border-radius: 1.3px;
    border: 0.2px solid rgba(1, 1, 1, 0);
}

input[type='range']::-moz-range-thumb {
    box-shadow: 0 0 0 rgba(0, 0, 0, 0), 0 0 0 rgba(13, 13, 13, 0);
    height: 15px;
    width: 15px;
    border-radius: 50px;
    background: #ffc600;
    cursor: pointer;
}

์ดˆ๊ธฐํ™”๋ฉด

๐ŸŒ ์ƒˆ๋กœ ์•Œ๊ฒŒ ๋œ ๊ฒƒ

videoํƒœ๊ทธ์˜ play, puase์ด๋ฒคํŠธ.

๊ฐ๊ฐ ์žฌ์ƒํ•˜๊ณ , ์ผ์‹œ์ •์ง€ํ•˜๋Š” ์ด๋ฒคํŠธ๋กœ, ์ด๋ฒคํŠธ๋ฅผ ์ง์ ‘ ๊ฑด๋“œ๋ฆด ์ˆ˜ ์žˆ๋Š” ์ง€ ์•Œ๊ฒŒ๋จ.

์ฐธ๊ณ  : https://developer.mozilla.org/en-US/docs/Web/API/HTMLMediaElement/play_event

style.flexBasis

ํ”Œ๋ ‰์Šค ์•„์ดํ…œ์˜ ์ดˆ๊ธฐ ํฌ๊ธฐ๋ฅผ ์ง€์ •ํ•จ.

box-sizing์„ ๋”ฐ๋กœ ์ง€์ •ํ•˜์ง€ ์•Š๋Š”๋‹ค๋ฉด ์ฝ˜ํ…์ธ  ๋ฐ•์Šค์˜ ํฌ๊ธฐ๋ฅผ ๋ณ€๊ฒฝํ•จ.

์ด๋ฒˆ ์˜ˆ์ œ์—์„œ๋Š” flexBasis๋ฅผ ๊ณ„์‚ฐํ•œ %๋กœ ์ง€์ •ํ•˜๋ฉด์„œ ๋™์˜์ƒ์ด ์ง„ํ–‰๋œ ์ •๋„๋ฅผ ๋‚˜ํƒ€๋‚ด๋Š”๋ฐ ์‚ฌ์šฉ.

์ฐธ๊ณ  : https://developer.mozilla.org/ko/docs/Web/CSS/flex-basis

๐ŸŒ ๊ณผ์ •

๐Ÿ‘‰ 0. ์‚ฌ์šฉํ•  ์—˜๋ฆฌ๋จผํŠธ๋“ค์„ ๊ฐ€์ ธ์˜ค๊ธฐ.

const player = document.querySelector('.player');
const video = player.querySelector('.viewer'); // player์•ˆ์— ์žˆ๋Š” viewer๋ฅผ ์ฐพ๊ธฐ๋•Œ๋ฌธ์— player.querySelector
const progress = player.querySelector('.progress');
const progressBar = player.querySelector('.progress__filled');

const toggle = player.querySelector('.toggle');
const skipButtons = player.querySelectorAll('[data-skip]');
const ranges = player.querySelectorAll('.player__slider');

๋ฉ”์†Œ๋“œ ์ž‘์„ฑ

๐Ÿ‘‰ 1. togglePlay()

function togglePlay() {
    // .play() or .pause()
    const method = video.paused ? 'play' : 'pause';
    video[method]();
    // if (video.paused) {
    //     video.play();
    // } else {
    //     video.pause();
    // }
}

video.addEventListener('click', togglePlay); // videoํ™”๋ฉด ํด๋ฆญ

video๋ฅผ ํด๋ฆญํ–ˆ์„ ๋•Œ play๋ฉด pause, pause๋ฉด play๋กœ ๋ณ€๊ฒฝ

if (video.paused) {
	video.play();
} else {
    video.pause();
}

๋ฅผ ์‚ผํ•ญ์—ฐ์‚ฐ์ž๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ ํ•œ์ค„๋กœ ์ž‘์„ฑ.

video.paused ? 'play' : 'pause';

๐Ÿ‘‰ 2. updateButton()

function updateButton() {
    const icon = this.paused ? 'โ–บ' : 'โš โš';
    toggle.textContent = icon; // ํ† ๊ธ€ ๋ฒ„ํŠผ
    // console.log('Update the button');
}

video.addEventListener('play', updateButton);
video.addEventListener('pause', updateButton);

๋น„๋””์˜ค์˜ ์žฌ์ƒ์ƒํƒœ์— ๋”ฐ๋ผ ์•„์ด์ฝ˜ ๋ณ€๊ฒฝ.
์ •์ง€ ์ƒํƒœ๋ฉด 'โ–บ', ์žฌ์ƒ์ƒํƒœ๋ฉด 'โš โš'

๐Ÿ‘‰ 3. skip()

<button data-skip="-10" class="player__button">ยซ 10s</button>
<button data-skip="25" class="player__button">25s ยป</button>

function skip() {
    // console.log('skipping!');
    console.timeLog(this.dataset.skip);
    video.currentTime += parseFloat(this.dataset.skip); // ํ˜„์žฌ์‹œ๊ฐ„์„ ์ถ”๊ฐ€ํ•˜๊ฑฐ๋‚˜ ๋บŒ. 
}

skipButtons.forEach(button => button.addEventListener('click', skip));

skipButtons๋“ค์— clickํ–ˆ์„ ๋•Œ ํ˜„์žฌ์‹œ๊ฐ„์—์„œ ์ง€์ •ํ•ด๋†“์€ skip๊ฐ’์„ ๋”ํ•ด์ค€๋‹ค.

๐Ÿ‘‰ 4. handleRangeUpdate()

function handleRangeUpdate() {    
  video[this.name] = this.value;
}
ranges.forEach(range => range.addEventListener('change', handleRangeUpdate));
ranges.forEach(range => range.addEventListener('mousemove', handleRangeUpdate));

๋ณผ๋ฅจ๊ณผ ์žฌ์ƒ์†๋„๋ฅผ ์กฐ์ ˆํ•จ.

๐Ÿ‘‰ 5. handleProgress()

function handleProgress() {
  const percent = (video.currentTime / video.duration) * 100;
  progressBar.style.flexBasis = `${percent}%`;
}
video.addEventListener('timeupdate', handleProgress); 

ํ˜„์žฌ ์žฌ์ƒ ์‹œ๊ฐ„์— ๋”ฐ๋ผ ์žฌ์ƒ๋ฐ”์˜ ์œ„์น˜๋ฅผ ์„ค์ •ํ•ด ์ฃผ๋Š” ํ•จ์ˆ˜.

video์˜ ํ˜„์žฌ์ง„ํ–‰๋œ ์‹œ๊ฐ„์„ ์ „์ฒด์‹œ๊ฐ„์— ๋‚˜๋ˆ„๊ณ  *100์„ ํ•˜์—ฌ ํผ์„ผํ…Œ์ด์ง€๋ฅผ ๊ตฌํ•จ.

๐Ÿ‘‰ 6. scrub()

function scrub(e) {
  const scrubTime = (e.offsetX / progress.offsetWidth) * video.duration;
  video.currentTime = scrubTime;}
let mousedown = false;
progress.addEventListener('click', scrub);
// progress.addEventListener('mousemove', scrub);
// true๋ฉด scrub false๋ฉด scurb X
progress.addEventListener('mousemove', (e)=>mousedown && scrub(e));
progress.addEventListener('mousedown', () => mousedown = true);
progress.addEventListener('mouseup', () => mousedown = false);

๋™์˜์ƒ์—์„œ ๋“œ๋ž˜๊ทธํ•˜๋Š” ๋™์•ˆ ๋ฐ”๋กœ๋ฐ”๋กœ ํ•ด๋‹น ์žฅ๋ฉด์œผ๋กœ ์ด๋™ํ•˜๊ฒŒ๋จ.

profile
๊นƒํ—ˆ๋ธŒ : github.com/JuneHyung

0๊ฐœ์˜ ๋Œ“๊ธ€