-
Custom HTML 5 Video Player 🎬🎥Web/JS30 2020. 11. 10. 20:29반응형
HTML5의 비디오 플레이어의 인터페이스는 고정되어있어서, 그 디자인을 바꾸는 게 불가능하다.
그래서, 기본 인터페이스를 숨기고 우리가 새로운 인터페이스를 직접 만들어서 사용해야한다.
우선, 전체 HTML 태그 구조는 다음과 같다.
기능 구현에 관한 예제이므로, CSS 코드는 올리지 않는다.
<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="-5" class="player__button">« 5s</button> <button data-skip="5" class="player__button">5s »</button> </div> </div>
우선, 자바스크립트 코드로 제어하기 위해서는 당연히 DOM Element들을 가져와서 변수에 담아야 한다.
const player = document.querySelector('.player'); const video = player.querySelector('.viewer'); 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) 플레이 버튼을 클릭했을 때 버튼의 모양이 바뀌고, 영상의 play/pause도 바뀌어야 한다.
2) 영상 자체를 클릭했을 때 버튼의 모양이 바뀌고, 영상의 play/pause도 바뀌어야 한다.
이를 위해서는 video.paused라는 프로퍼티를 이용해서 영상의 재생 여부를 검사한 후
영상이 재생중이라면 pause(), 멈춰져있다면 play()를 호출해주면 된다.
function togglePlay() { if(video.paused) { // playing이라는 property는 없다. 'paused'만 있음. video.play(); } else { video.pause(); } } video.addEventListener('click', togglePlay); toggle.addEventListener('click', togglePlay);
참고로, play()나 pause() 역시 video에 속한 하나의 메서드임을 이해하고 있다면, 다음과 같은 방법도 사용이 가능하다.
물론 ternary operator를 사용하면 코드의 길이를 줄일 수 있지만, 너무 과도하게 사용하면 가독성이 떨어진다.
const method = video.paused ? 'play' : 'pause'; // 메서드 이름 video[method](); // 메서드 호출 // 이런 것도 가능. 근데 가독성이 좀 떨어짐 video[video.paused ? 'play' : 'pause']();
버튼의 모양을 바꾸는 로직은 togglePlay() 함수 안에다가 구현해줘도 되긴 한다.
하지만, 비디오를 pause하는 방법이 마우스 클릭만 있는 건 아니다.
나중에 어떤 필요에 의해 어떤 방법을 구현할 지는 아무도 모르는 것이기 때문에
'비디오가 pause되는 이벤트' 자체에 별도의 리스너를 붙여주는 것이 현명하다.
function updateButton() { // 이 리스너 자체가 video에 붙기 때문에, this가 video를 가리킨다. const icon = this.paused ? '►' : '❚❚'; toggle.textContent = icon; } video.addEventListener('play', updateButton); video.addEventListener('pause', updateButton);
스킵 버튼 기능 구현
위의 HTML 코드를 자세히 보면, 스킵 버튼 각각에 'data-skip'이라는 attribute로
한 번에 어느 정도를 스킵할 것인지를 dataset에 저장해놓은 것을 알 수 있다.
그래서 자바스크립트에서 이 dataset을 불러오면 손쉽게 기능 구현이 가능하다.
function skip() { video.currentTime += parseFloat(this.dataset.skip); } skipButtons.forEach(button => button.addEventListener('click', skip));
버튼이 두 개라서, forEach를 이용해서 한 번에 리스너를 붙여주었다.
그리고, dataset에 들어가 있는 데이터는 문자열이므로 parseFloat를 이용하여 숫자로 바꿔주었다.
볼륨 기능과 배속 기능 구현
볼륨 기능과 배속 기능은 둘 다 동일하게 range input으로 되어있다.
그리고 각각의 range input의 name 속성이 'volume'과 'playbackRate'로 되어있는데,
이는 비디오에 존재하는 프로퍼티 명과 완전히 동일하다.
그래서, 해당 이벤트 리스너 내부에서 'this.name'을 불러오면, 해당 속성에 손쉽게 접근이 가능하다.
이렇게, name을 프로퍼티 명과 동일하게 설정해놓는 것은 코드를 간단하게 만들어주는 팁.
function handleRangeUpdate() { video[this.name] = this.value; } ranges.forEach(range => range.addEventListener('change', handleRangeUpdate)); // 버튼을 놓지 않고 움직이는 도중에도 변화가 실시간으로 적용되어야 함. ranges.forEach(range => range.addEventListener('mousemove', handleRangeUpdate));
volume과 playbackRate가 video의 프로퍼티 이름이라고 했으므로, video[프로퍼티명]으로 접근이 가능하다.
참고로, range input의 'change' 이벤트는 마우스를 누르고 움직이는 동안에는 발생하지 않는다.
마우스를 놓는 순간 발생하는 이벤트가 change 이벤트이기 때문에,
input을 놓지 않고 움직이는 동안에도 실시간으로 변화가 적용되도록 하기 위해서는
mousemove 이벤트에도 동일한 리스너를 붙여주도록 하자.
프로그레스 바 구현
1) 우선, progress bar가 영상의 재생 길이에 따라 변화하도록 만들어보자.
CSS 파일을 뜯어보면, 현재 progress bar의 길이는 flex-basis 속성을 통해 조절되게끔 구현되어있다.
그래서 전체 영상 길이에 대한 현재 재생 길이의 비율을 퍼센티지로 구해서 flex-basis에다가 넣어주기로 한다.
function handleProgress() { const percent = (video.currentTime / video.duration) * 100; progressBar.style.flexBasis = `${percent}%`; } video.addEventListener('timeupdate', handleProgress);
timeupdate는 video.currentTime, 즉 영상의 재생 시간에 변화가 생길 때마다 호출되는 이벤트이다.
비슷한 이벤트로 'progress'라는 이벤트도 있는데, 무슨 차이점이 있는지는 정확히 모르겠다.
둘 다 영상이 재생되는 동안 발생하고, 영상의 재생이 멈추면 발생하지 않는다.
2) 이제, progress bar를 마우스로 조절할 수 있도록 만들어보자.
progress bar는 position이 relative로 설정되어있기 때문에, progress bar의 아무데나 마우스를 클릭해보면
해당 이벤트가 발생하는 offsetX가 전체 video의 width를 기준으로 계산된다는 것을 알 수 있다.
우리가 progress bar를 클릭했을 때, 그 지점이 전체 영상의 어디쯤인지를 계산하기 위해서는
현재 클릭한 곳의 offsetX를 전체 video의 offsetWidth로 나눈 후, 거기다가 영상의 duration을 곱해주면 된다.
function scrub(e) { const scrubTime = (e.offsetX / progress.offsetWidth) * video.duration; video.currentTime = scrubTime; }
이제 이 scrub이라는 리스너를 이벤트에 붙여줘야 하는데
progress.addEventListener('click', scrub); progress.addEventListener('mousemove', scrub);
요렇게 하고 나서 테스트를 해보면, 클릭을 하지 않았는데도 progress bar 위에서 마우스를 움직이기만 하면
영상이 마구마구 이동해버리는 모습을 볼 수 있을 것이다.
그래서, 현재 내가 마우스를 클릭하고 있는 상태인지 아닌지를 flag 변수를 이용해서 기억해주자.
HTML Canvas 예제와 동일한 경우의 flag 변수라고 볼 수 있다.
let mousedown = false; progress.addEventListener('click', scrub); progress.addEventListener('mousemove', (e) => mousedown && scrub(e)); progress.addEventListener('mousedown', () => mousedown = true); progress.addEventListener('mouseup', () => mousedown = false);
이렇게, mousedown이라는 별도의 flag 변수를 만들어서, mousedown 이벤트 발생 시에는 true로,
mouseup 이벤트 발생 시에는 false로 만들어주면 된다.
참고로 'mousedown && scrub(e)' 라고 해주면, mousedown 변수의 값이 true일 때만
scrub(e) 함수가 호출되도록 만들어줄 수 있다.
항상, 만든 결과를 보고 나면 간단하게 느껴지지만
정작 혼자 만들려고 하면, 프로퍼티명과 메서드명을 몰라서 한참을 헤맨다.
계속 다루다보면 어느 정도 외워져서 좀 간단해지려나.
개선할 점은 '전체화면 버튼' 정도가 있겠다.
나중에 시간이 나면 만들어보는 걸로.
참고
HTML 미디어 태그(video/audio)와 관련된 property, method
반응형'Web > JS30' 카테고리의 다른 글
Slide In on Scroll 💻 (0) 2020.11.12 Key Sequence Detection(KONAMI CODE) 🎮🎲 (0) 2020.11.11 Hold Shift to Check Multiple Checkboxes 📦 (0) 2020.11.09 14 Must Know Dev Tools Tricks 💪 (0) 2020.11.04 Fun with HTML5 Canvas 🎨 (0) 2020.11.01