카테고리 없음

[TIL] Day 31 React에서 audio 태그로 음악 플레이어 만들기

y.developer 2023. 11. 16. 23:40
728x90

2023.11.16 목

 

현재 작업 중인 그룹 아티스트 팬레터함 홈페이지에 배경음악을 넣어보기로 했다.

영상을 다루는 방법은 알고 있었지만, 음악은 처음이었다.

그리고 리액트에서는 또 약간은 다른 방법으로 작동을 해서 음악을 재생시키는 것부터 컨트롤박스를 디자인하는 것까지 쉽지는 않았지만 완성했다.

 

 

 

전체적인 콘셉은 '신곡 발표 홈페이지'로 정했다.

르세라핌의 신곡인 'Perfect Night'를 소개하는 페이지.

각 멤버들에게 팬레터를 작성할 수 있다.

신곡 발표라는 콘셉에 적합하게 음악을 넣어보면 좋겠다는 생각이 들어서 만들게 되었다.

 

 

음악 준비

일단 음악 파일과 코드를 작성할 컴포넌트를 만들어 준다.

음악은 mp3 형식으로 준비했으며, Music.jsx 컴포넌트를 만들어주었다.

컴포넌트 파일에 음악 파일을 import 해준다.

sec에 직접 경로를 입력하니까 인식하지를 못 한다.

import 한 후 사용하자.

 

import Audio1 from "assets/PerfectNight.mp3";

 

 

audio 태그

<audio className="music-audio" src={Audio1} type='audio.mp3' controls/>
// autoPlay
// muted
// loop
// onLoadStart={e => e.target.volume = 0.25}

원하는 위치에 audio 태그를 생성해 주고 옵션 값을 설정해 준다.

 

src에 import한 음악 파일의 경로를 넣어준다.

리액트에서 자바스크립트 문법을 사용하기 위해서는 { } 안에 작성해야 한다.

type은 각자 파일 형식에 맞는 것으로 설정한다.

 

 

controls 옵션을 넣게 되면 자체적으로 제공하는 컨트롤러 UI가 제공된다.

근데 사실... 사진을 봐도 알다시피 그렇게 예쁘진 않다...

이 부분은 자바스크립트에서 요소 하나하나에 접근하여 컨트롤러를 세팅하고 CSS로 스타일링이 가능하다.

 

자! 여기까지만 해도 기본적으로 '배경음악 삽입'은 성공했다!

이제 여기서 조금씩 더 디벨롭해 보자.

 

 

audio 태그 옵션

controls

기본적으로 제공하는 컨트롤러를 나타낼 수 있다.

이 옵션을 작성하지 않으면 따로 음악을 컨트롤할 수 없다.

하지만 못생긴 컨트롤러를 그대로 놔두는 것보단 안 보이게 하는 게 더 나을 수도...

 

 

autoPlay

controls가 없으면 음악은 어떻게 재생시킬까?

페이지가 로드될 때 기본값으로 자동재생되도록 해주는 옵션이다.

여기서 유의할 점! 

JS에서는 대문자 상관없이 'autoplay'로 작성할 것이다.

하지만, react에서는 대문자 구문을 필수적으로 해줘야 한다.

물론 콘솔창에 경고를 해주고 자체적으로 해석을 진행하지만 안 될 때도 있다... (이 부분 때문에 시간 낭비를 좀 많이 했다...)

react에서는 'autoPlay'로 적어주자!

그리고 autoPlay가 작동하지 않는 경우가 또 있다.

확실하진 않지만 JS 코드에서 audio.play() 혹은 audio.pause()를 다룬다면 해당 옵션이 기본값으로 작동하지 않을 수 있다.

 

 

muted

음악의 볼륨을 0으로 만드는 즉, mute 시키는 옵션이다.

음악 재생 여부와 음악의 볼륨은 다른 부분이다!

즉, autoPlay 옵션이 주어져서 음악은 재생될지라도 muted 설정이 되어있다면 음소거된 상태로 음악이 재생되는 것이다.

autoPlay 설정을 했는데 음악이 안 들린다면 muted 설정이 되어있는 것은 아닌지 의심해 보자.

 

 

loop

음악 재생이 끝까지 완료되면 다시 반복 재생하는 기능이다.

음악이 끊기지 않고 계속 흘러나오길 원한다면 설정하자.

 

 

onLoadStart

이 부분에서 정말 많은 고민을 했고, 많은 정보를 찾아봤다.

일단 autoPlay를 주게 되었을 때, 처음 음악재생 시 소리가 너무 커서 UX가 좋지 않았다.

초기 볼륨 값을 조절해 주면 되겠다는 간단한 해결책을 생각했으나 코드로 적용시키기에는 간단하지 않았다.

무엇보다 리액트에서 audio를 다루는 부분은 정보가 많지 않았다... (전부 JS에서 다루는 내용)

리액트에 익숙했다면 바로 적용시킬 수 있었겠지만, 리액트에 적응하고 있는 시기라서 쉽지만은 않았다.

수많은 정보를 찾아보고, 다 적용시켜 봤지만 계속 에러만 떠서 포기하려 했으나 끝까지 해보기로 했다.

(음악을 꼭 넣고 싶었다!)

 

일단, 가장 결정적으로 애매하게 헷갈렸던 부분은 대문자 구문이다.

위에서 말했듯이 JS에서는 'onloadstart'로 작성했을 것이다.

하지만 react에서는 'onLoadStart'로 작성해야 한다.

이 부분은 콜솔창에서도 자세하게 나오지 않고 에러만 계속 떠서 너무 헷갈렸다.

특히 뒷부분 코드가 틀린 건지 대문자를 작성하는 부분이 틀린건지 알 수가 없었다.

최대한 변수를 줄여가며 디버깅을 하기 위해서라도 확실하게 'onLoadStart'로 작성하고 시작하자!

 

이제 볼륨 옵션에 접근하는 방법이다.

많은 검색을 해보면서 this.volume으로 접근하는 방법이 대부분이었다.

시도해 봤으나 근데 다 실패했다.

JS 문법을 쓰기 위한 { } 가 틀렸는지, 다른 설정값이 누락되었는지 알 수가 없었다.

react에서 this를 쓰려면 this 바인딩을 해야 된다... 뭐 그런 내용도 있었지만...

내가 원하는 구조는 아니었고, 심지어 audio를 다루는 것도 아닌 addEventListener()에 대한 내용이 대부분이었다.

 

그러던 중 e.target.volume라는 희망을 만났다.

이렇게 접근하는 방법이 있다는 것만 알아도 훨씬 쉬웠을 텐데 그 과정이 너무 오래 걸렸고 힘들었다.

그래서 완성된 코드는 onLoadStart={e => e.target.volume = 0.25} 이거다.

볼륨값에 접근하여 초기 볼륨값을 0.25로 설정했다.

볼륨값의 범위는 0~1 사이로 설정가능하다.

 

audio 태그 외에도 iframe 태그로도 음악을 삽입할 수 있지만,

원하는 만큼 기능을 활용하지 못해서 audio 태그를 사용했다.

<iframe id="audio" src={Audio1} allow='autoplay'></iframe>

 

 

 

음악 컨트롤러 만들기

기본적으로 제공되는 UI를 활용해도 기능적인 측면에서는 충분하다.

그러나 예쁘지가 않다.

간단하게 디자인해 보고, 아주 기본적인 기능들만 구현해 보기로 했다.

 

아직 리액트에서 배울게 많다.

심지어 현재 프로젝트도 초반 단계라서 완벽하게 코드가 만들어진 것도 아니고, 불필요한 코드도 있을 수도 있지만,

기능적인 측면을 어떻게 다루는지 적어보도록 하자.

현재 코드에서는 document.querySelector를 사용했다.

이 부분은 추후 useRef를 배우게 되면 리팩터링 해야 하는 부분이다.

따라서 아주 자세하게는 다루지 않고 이렇게만 접근하면 되겠다는 느낌으로 작성하는 부분이다.

 

Music.jsx

더보기
import React from 'react'
import styled from 'styled-components';
import Audio1 from "assets/PerfectNight.mp3";
import LESSERAFIM1 from 'assets/르세라핌1.webp';

.
.
// styled-components
.
.

window.onload = function () {
  let musicPlayItems = document.querySelectorAll(".music-play-item")
  const palyBox = document.querySelector(".paly-box")
  const nowPlay = document.querySelector(".now-play")
  const upBtn = document.querySelector(".upBtn")
  const downBtn = document.querySelector(".downBtn")

  musicPlayItems.forEach(function (item) {
    item.audio = item.querySelector("audio")
    item.audio.play()
    // item.audio.pause()

    upBtn.addEventListener("click", function () {
      if (item.audio.volume < 0.94) {
        item.audio.volume += 0.05
        downBtn.style.color = "#FBA1B7"
      } else {
        upBtn.style.color = "#aa6d7c"
      }
    })

    downBtn.addEventListener("click", function () {
      if (item.audio.volume > 0.01) {
        item.audio.volume -= 0.05
        upBtn.style.color = "#FBA1B7"
      } else {
        downBtn.style.color = "#aa6d7c"
      }
    })

    item.addEventListener("click", function () {
      let nowTime = item.audio.currentTime
      if (this.isPlaying) {
        palyBox.innerHTML = "🎧"
        upBtn.style.display = "block"
        downBtn.style.display = "block"
        item.audio.play()
      } else {
        palyBox.innerHTML = "⏸"
        upBtn.style.display = "none"
        downBtn.style.display = "none"
        item.audio.pause()
        item.audio.currentTime = nowTime
      }

      item.isPlaying = !item.isPlaying
    })
  })
}


function Music() {
  return (
    <MusicCSS>
      <li className="music-play-item">
        <img
          src={LESSERAFIM1}
          alt="르세라핌" lang="ko"
          className="music-album-cover" />
        <div className="music-info">
          <div className="music-info-detail">
            <div>
              <h1>
                Perfect Night
              </h1>
              <strong>
                LE SSERAFIM
              </strong>
            </div>
            <div className='paly-box'>
              <p className='paly-pause'>Click To<br />PAUSE</p>
            </div>
          </div>
          {/* controls muted autoPlay*/}
          <audio className="music-audio" src={Audio1} type='audio.mp3' onLoadStart={e => e.target.volume = 0.25} controls loop />
        </div>
      </li>
      <div className="now-play">
        <button className='upBtn'>▲</button >
        <button className='downBtn'>▼</button>
      </div>
    </MusicCSS>
  )
}

export default Music

 

가장 먼저 마주쳤던 오류는 로직 순서에 대한 시점 차이다.

즉, 태그에 설정된 className이 로드되기 전에 querySelector를 통해서 값을 가져오게 되니 오류가 뜨는 것 같다.

이 부분이 react에서 querySelector를 쓰기 어려운 부분인 것 같다.

당장 useRef나 다른 방법으로 수정할 수 없어서 임시방편으로 window.onload를 사용해서 오류를 해결했다.

모든 태그가 로드된 후 해당 함수가 작동하도록 하는 코드인 것 같다. 

 

모든 item에 접근하는 musicPlayItems 요소에 forEach를 돌린다.

해당하는 item요소에 audio를 붙이면 음악 설정을 컨트롤할 수 있다.

 

 

play & pause

 

item.audio.play() : 음악 재생

item.audio.pause() : 음악 일시정지

 

해당 itemaddEventListener를 통해서 클릭 시 해당 설정이 작동하도록 할 수 있다.

해당 요소를 누르면 일지정지가 되도록 하는 play&pause 기능을 만들어보았다.

이때 중요한 것은 현재 재생되고 있는 음악의 시간을 가져오는 것이다.

이것이 귀찮다면 pause 시킬 때 시간값을 0으로 주면 된다. 처음부터 재생되도록.

그게 아니라면 꼭 if문 밖에서 currentTime 값을 저장해 주자.

 

let nowTime = item.audio.currentTime

 

저장을 한 후 pasue에 해당하는 부분을 작성할 때

이 코드 세트를 작성해 주면 된다.

 

item.audio.pause()

item.audio.currentTime = nowTime

 

여기까지 작성했다면 play&pause 기능은 구현 완료한 것이다!

 

 

volume up & down

초기 볼륨 값을 많이 낮춰놓은 상태이기 때문에 클라이언트의 볼륨값에 따라서 너무 작게 들릴 수도 있다.

따라서 볼륨 버튼도 만들어 주는 것이 좋다.

각 버튼을 생성해 준 후 addEventListener를 적용시켜 주자.

 

item.audio.volume = 볼륨수치 [0~1]

 

볼륨값에 접근이 이렇게 하면 된다.

주의해야 할 점은 볼륨수치 [0~1]에서 벗어가게 되면 오류가 발생한다...

볼륨이 최소 or 최대가 되었을 때 이상한 0.000000002인가... 값이 붙게 된다.

해당 범위에 접근하지 않도록 하는 로직이 추가적으로 필요해 보인다.

 

 

음악 컨트롤러 디자인

기본 기능이 있음에도 왜 기능을 컨트롤하는 코드를 작성했는가!

바로 예쁘게 만들기 위해서.

대미를 장식할 CSS를 다뤄보자.

react에서 많이 사용하는 styled-components를 사용해 볼 것이다.

 

Music.jsx / styled-components 부분

더보기
const MusicCSS = styled.div`
  display: flex;
  flex-direction: row;
  align-items: center;
  width: 330px;
  margin: 10px;
  scale: 90%;

  .music-play-item {
    position: relative;
    display: flex;
    align-items: center;
    align-content: center;
    width: 100%;
  }

  .music-play-item * {
    transition: opacity 120ms;
    transition: scale 120ms;
  }

  .music-play-item:hover * {
    opacity: 0.6;
  }

  .music-play-item:active * {
    scale: 0.9;
  }

  .music-album-cover {
    width: 50px;
    height: 50px;
    border-radius: 3px;
    margin-right: 16px;
  }

  .music-info {
    flex-grow: 1;
    display: flex;
    justify-content: space-between;
    align-content: center;
    align-items: center;
    padding: 8px 0;
    border-top: 1px solid #e5e5e5;
    border-bottom: 1px solid #e5e5e5;
    cursor: pointer;
  }

  .music-info-detail {
    display: flex;
    flex-direction: row;
    align-items: center;
    justify-content: space-between;
    width: 100%;
    margin:0 10px;
  }

  .music-info-detail h1 {
    font-size: 16px;
    line-height: 1.2;
    color: #383838;
  }

  .music-info-detail strong {
    display: block;
    font-size: 14px;
    color: #8e8e93;
    margin-top: 5px;
  }

  .music-info-detail p {
    display: block;
    font-size: 14px;
    color: #FBA1B7;
    text-align: center;
    font-weight: 600;
    font-size: 10px;
    border: 1px solid #FBA1B7;
    border-radius: 5px;
    padding: 5px;
  }

  .now-play {
    display: flex;
    flex-direction: column;
    transition-duration: 120ms;
  }

  .now-play button {
    color: #FBA1B7;
    font-size: 17px;
    text-shadow: 0px 2px 5px #b18f97;
    background: transparent;
    border: none;
    transition: scale 120ms;
    cursor: pointer;
  }

  .now-play button:hover {
    scale: 1.4;
  }

  .now-play button:active {
    scale: 1.2;
  }

  audio {
    display: none;
  }
`

 

앨범 사진, 제목, 아티스트, play&pause 상태, 볼륨 버튼을 만들었다.

hoveractive 설정 및 색상변경을 추가하여 동적으로 만들었다.

transition 속도를 추가하는 것도 디테일을 잡는데 큰 도움이 될 것이다.

 

 

 

초기 화면

 

pause
play

 

 


하루를 마치며

비록 부족한 코드지만, 내가 정보를 찾았을 때 헤맸던 것처럼 이 정보가 필요한 사람이 분명 있을 것이라고 생각한다.

코드가 완성되고 리팩터링을 진행하게 된다면 추가된 부분을 다시 다뤄봐야겠다.

지난번 영상 때보다 더 깊게, 음악 컨트롤러를 만들면서까지 배경음악 추가에 대해서 배울 수 있었던 시간이었다.

이제 다른 프로젝트를 할 때에도 배경음악 컨트롤은 쉽게 할 수 있을 것이다.

 

 

오늘의 한 줄
오늘 밤은 정말
Perfect Night

 

728x90