[Javascript 30] Day 11 - HTML5 영상 플레이어

이사감·2021년 2월 7일
2

Javascript 30

목록 보기
11/15
post-thumbnail

Day 11 - HTML5 basic video player

바닐라 자바스크립트로 비디오 플레이어의 기본적인 기능들을 구현해보는 Day 11

요구사항

1. 재생/멈춤

  • 비디오 화면 클릭시 재생/멈춤
  • 비디오 재생/멈춤 버튼 클릭시 재생/멈춤
  • 재생/멈춤은 스페이스 키로도 작동
  • 재생 시에는 ❚❚으로, 멈춤 시에는 ►으로 버튼 아이콘 변경
  • 재생시 progress 바로 재생 현황(?) 표현

2. 콘트롤 작동

  • 음량바 클릭 및 드래그앤 드랍으로 음량 조절
  • 음량은 위/아래 키 조작으로도 조절 가능
  • 재생속도바 클릭 및 드래그앤 드랍으로 재생 속도 조절
  • 앞/뒤 스킵 버튼 클릭시 -10초, +25초 재생 좌표 이동
  • 재생 좌표 이동은 좌/우 키 조작으로도 이동 가능
  • 재생 좌표 이동은 클릭 및 드래그앤 드랍으로도 가능

How To

키보드로 조작하기

  • 재생/멈춤
//비디오가 재생중이면 => 멈춤
  비디오가 멈춰있으면 => 재생 하도록 작성된 함수
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));

TIL

flex-basis

MDN
day 05에서 보았던 flex 축약형 속성 (ex. flex: 1,1,1;)에서 세번째 값이다. flex 아이템의 공간 배분 전 초기 크기를 지정한다.
flex-direction이 row이면 너비를, column이면 높이를 지정한다.
box-sizing을 따로 지정하지 않는다면 콘텐츠 박스의 크기를 변경한다.

flex-basis: auto;

  • 기본값. 단, flex 축약형에서는 생략하면 auto가 아니라 0이 된다.
  • flex 아이템의 width를 사용
  • flex-basis가 auto가 아닐 때, width 값도 동시 적용된다면 flex-basis가 우선한다.

flex-basis: content;

  • flex 아이템의 콘텐츠의 크기에 따라 자동으로 크기가 변한다.
  • width를 따로 설정하지 않은 경우와 같다.

flex-basis: 값;

  • 값은 0, 10em, 200px, 50% 등이 올 수 있으나 음수는 올 수 없다.
  • 여백이 아닌 영역 자체를 원하는 비율로 분할하고자 할 때 0으로 처리한다.

parseFloat()

string을 실수로 바꾸는 함수. 소수점까지 다 나타낸다.
띄어쓰기로 여러 수가 있으면 첫번째 수만 실수로 바꾼다.
공백으로 시작하면 공백은 무시한다.
수가 아닌 문자로 시작하면 NaN을 반환한다.

HTML 오디오/비디오 메소드

  • load() : 오디오/비디오를 리로드한다.
  • play()
  • pause()

더 많은 메소드는 w3schools 참고

HTML 오디오/비디오 속성

  • played
  • paused
  • volume
  • duration : 총 길이를 초 단위로 나타낸다
  • currentTime : 현재 재생 위치를 초 단위로 나타낸다
  • currentSrc : 현재 오디오/비디오의 url을 나타낸다
  • playbackRate : 재생 속도를 나타낸다

더 많은 속성은 w3schools 참고

timeupdate, progress 이벤트

MDN
w3schools

둘 모두 HTML의 미디어 요소에 발생하는 이벤트인데,
timeupdate 이벤트는 currentTime이 업데이트 될 때 발생한다.
progress 이벤트는 브라우저가 비디오를 다운로드할 때 발생하고 다음과 같은 순서를 따른다.

  1. loadstart
  2. durationchange
  3. loadedmetadata
  4. loadeddata
  5. progress
  6. canplay
  7. 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);
profile
https://emewjin.github.io

0개의 댓글