바닐라 자바스크립트로 비디오 플레이어의 기본적인 기능들을 구현해보는 Day 11
1. 재생/멈춤
2. 콘트롤 작동
- 재생/멈춤
//비디오가 재생중이면 => 멈춤 비디오가 멈춰있으면 => 재생 하도록 작성된 함수 function togglePlay(){ if(video.paused){ video.play() } else { video.pause() } } //이 함수가 스페이스 키를 눌렀을 때도 작동하도록 keyMove함수 작성 function keyMove(e){ if (e.which === 32){ // console.log('space'); togglePlay(); } } //키가 눌려지는 이벤트를 감지하여 keyMove함수 실행 window.addEventListener("keydown", keyMove);
- 볼륨 조절
// 상/하 방향키가 눌리면 볼륨 range에 focus되게 작성. range에 focus되면 자연스럽게 키조작으로 볼륨 조절 가능 function volumeMove(e){ if(e.which === 38 || e.which === 40){ ranges[0].focus(); } } // 또는 range를 건드리지 않고 비디오 자체 속성으로 볼륨 조절 function volumeMove(e){ if(e.which === 38 && video.volume < 1){ video.volume = video.volume + 0.05; console.log(video.volume); } if(e.which === 40 && video.volume > 0.1){ video.volume = video.volume - 0.05; console.log(video.volume); } } window.addEventListener("keydown", volumeMove);
- 재생 위치 이동
// html <button data-skip="-10" class="player__button">« 10s</button> <button data-skip="25" class="player__button">25s »</button> // js const skipButtons = player.querySelectorAll('[data-skip]'); // 일단은 통일성을 위해서 비디오 콘트롤의 스킵 버튼과 동일한 시간을 스킵하도록 작성함 좌/우 키가 눌리면 -10초, +25초 이동 단, 10초 미만일 경우나 재생이 거의 끝났을 때에는 더이상 이동할 수 없으므로 이에 대한 조건을 부여함 function keyMove(e){ if (e.which === 37 && video.currentTime > 10){ video.currentTime += parseFloat(skipButtons[0].dataset.skip); } if (e.which === 39 && video.currentTime < 570){ video.currentTime += parseFloat(skipButtons[1].dataset.skip); } } window.addEventListener("keydown", keyMove);
//html <button class="player__button toggle" title="Toggle Play">►</button> //js const toggle = player.querySelector('.toggle'); function updateToggle(){ if (this.paused) { // console.log('play'); // console.log들은 모두 작성 과정에서 이들이 제대로 작동하고 있는지 확인하기 위해 사용했습니다. toggle.textContent = '►'; } else { // console.log('pause'); toggle.textContent = '❚❚'; } } video.addEventListener("play", updateToggle); video.addEventListener("pause", updateToggle);
약간의 수학이 필요합니다.
영상 재생 현황? 진도?를 보여주는 노란색 progress bar의 html과 css는 다음과 같이 작성되어 있는데,//html <div class="progress"> <div class="progress__filled"></div> </div> //css .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%; }
flex-basis
로 노란 게이지 바를 조절합니다. 따라서flex-basis
값을 자바스크립트로 영상 재생 진도에 맞춰 수정해주어야 합니다.
이 값은 %로 다루어지고 있으므로 현 재생 위치를 다음과 같이 %로 변환하여 percent 변수에 저장후,flex-basis
값에 대입합니다.//js function handleProgress(){ const percent = (video.currentTime/video.duration)*100 // console.log(percent); progressBar.style.flexBasis = `${percent}%`; } video.addEventListener("timeupdate", handleProgress);
- 클릭
마찬가지로 약간의 수학이 필요합니다.function scrub(e){ const place = (e.offsetX / progress.offsetWidth) * video.duration; video.currentTime = place; } progress.addEventListener("click", scrub);
progress 바의 정중앙 클릭시 영상이 총 길이의 50%부터 재생되게 하고 싶습니다.
이를 위해 progress 바 정중앙 좌표인 X축 값을 영상 시간으로 변환해서,video.currentTime
에 대입해야 합니다.
정중앙이므로 X축 값은 전체 길이의 50%였을 겁니다 :(e.offsetX / progress.offsetWidth)
만일 영상의 전체 시간이 1분이라면, 정중앙은 30초를 나타냅니다 :(e.offsetX / progress.offsetWidth) * video.duration
이를 변수 place로 받아video.currentTime
에 대입합니다. 클릭시에는 단순하게 click 이벤트를 사용하면 되지만, 드래그앤 드랍은 조금 더 복잡합니다.
- 드래그앤 드랍
플래그 제어를 활용합니다.let mousedown = false; progress.addEventListener("mousemove", (e) => mousedown && scrub(e)); progress.addEventListener("mousedown", ()=> mousedown = true); progress.addEventListener("mouseup", ()=> mousedown = false);
드래그시에는 계속 마우스 좌측을 클릭(mousedown)하고 있으므로, 이때 플래그를 true로 전환합니다. 드랍시에는 마우스 클릭을 떼니까 false로 전환합니다.
드래그 자체가 마우스 좌측 클릭(mousedown) + 마우스 이동(mousemove) 두 가지로 이루어져 있으므로, mousemove가 일어났을 때 mousedown까지 true여야 재생 좌표 이동이 발생하게 합니다.
이를 다음과 같이 작성할 수 있으나,progress.addEventListener("mousemove", (e) => { if(mousedown){ scrub(e) } });
이렇게 하면 더 쉽고 간단하게 작성할 수 있습니다.
progress.addEventListener("mousemove", (e) => mousedown && scrub(e));
MDN
day 05에서 보았던 flex 축약형 속성 (ex. flex: 1,1,1;
)에서 세번째 값이다. flex 아이템의 공간 배분 전 초기 크기를 지정한다.
flex-direction이 row이면 너비를, column이면 높이를 지정한다.
box-sizing
을 따로 지정하지 않는다면 콘텐츠 박스의 크기를 변경한다.
flex-basis: auto;
flex-basis: content;
flex-basis: 값;
string을 실수로 바꾸는 함수. 소수점까지 다 나타낸다.
띄어쓰기로 여러 수가 있으면 첫번째 수만 실수로 바꾼다.
공백으로 시작하면 공백은 무시한다.
수가 아닌 문자로 시작하면 NaN을 반환한다.
더 많은 메소드는 w3schools 참고
더 많은 속성은 w3schools 참고
둘 모두 HTML의 미디어 요소에 발생하는 이벤트인데,
timeupdate 이벤트는 currentTime이 업데이트 될 때 발생한다.
progress 이벤트는 브라우저가 비디오를 다운로드할 때 발생하고 다음과 같은 순서를 따른다.
- loadstart
- durationchange
- loadedmetadata
- loadeddata
- progress
- canplay
- canplaythrough
// elements
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');
// functions
//재생/멈춤 기능
function togglePlay(){
if(video.paused){
video.play()
} else {
video.pause()
}
}
//재생/멈춤 아이콘 변경 기능
function updateToggle(){
if (this.paused) {
// console.log('play');
toggle.textContent = '►';
} else {
// console.log('pause');
toggle.textContent = '❚❚';
}
}
//재생 좌표 버튼으로 이동 기능
function skip(){
console.log(this.dataset.skip);
video.currentTime += parseFloat(this.dataset.skip);
}
//비디오 콘트롤 볼륨&재생속도 조절 기능
function handleRangeUpdate(){
video[this.name]=this.value;
// console.log(this.value);
// console.log(this.name);
}
//영상 재생 현황?진도? 보여주기 기능
function handleProgress(){
const percent = (video.currentTime/video.duration)*100
// console.log(percent);
progressBar.style.flexBasis = `${percent}%`;
}
//재생 좌표 이동 기능
function scrub(e){
const place = (e.offsetX / progress.offsetWidth) * video.duration;
video.currentTime = place;
}
//위의 기능들 키보드 조작으로 하기
function keyMove(e){
if (e.which === 37 && video.currentTime > 10){
video.currentTime += parseFloat(skipButtons[0].dataset.skip);
}
if (e.which === 39 && video.currentTime < 570){
video.currentTime += parseFloat(skipButtons[1].dataset.skip);
}
if (e.which === 32){
// console.log('space');
togglePlay();
}
}
function volumeMove(e){
if(e.which === 38 || e.which === 40){
ranges[0].focus();
}
}
// event listners
video.addEventListener("click", togglePlay);
video.addEventListener("play", updateToggle);
video.addEventListener("pause", updateToggle);
video.addEventListener("timeupdate", handleProgress);
toggle.addEventListener("click", togglePlay);
skipButtons.forEach(button=> button.addEventListener("click", skip));
ranges.forEach(range=>range.addEventListener("change", handleRangeUpdate));
ranges.forEach(range=>range.addEventListener("mousemove", handleRangeUpdate));
let mousedown = false;
progress.addEventListener("click", scrub);
progress.addEventListener("mousemove", (e) => mousedown && scrub(e));
progress.addEventListener("mousedown", ()=> mousedown = true);
progress.addEventListener("mouseup", ()=> mousedown = false);
window.addEventListener("keydown", keyMove);
window.addEventListener("keydown", volumeMove);