마이크 사운드바 개발하기

adultlee·2023년 12월 19일
2

NDD

목록 보기
12/16
post-thumbnail
post-custom-banner

저는 무료 면접 서비스 곰터뷰를 개발 중입니다.

서문

매주 진행되는 FE 마스터 클래스에서 마스터이신 준일님께서 인터뷰 페이지에서 유저에게 마이크의 audio를 시각적으로 확인할 수 있는 기능을 제공하면 어떨지에 대한 피드백이 있었습니다.

저 또한 굉장히 재밌을 것이라 생각을 했고, 앞선 팀 피드백을 바탕으로 진행되었던 만큼, 앞 팀의 프로젝트인 Boarlog의 UI에서 영감을 받았습니다.

해당 컴포넌트는 상당히 직관적이었지만, 기왕 만드는 기에 좀 더 다른 UI를 제공하고 싶었습니다.

어떻게 개발했는가?

1. 전역으로 사용하는 media를 인자로 받는다.

당연하겠지만 마이크에 입력되는 데이터에 따라서 sound volume 을 측정해야하니 mediaStream 을 인자로 받아 입력받아야만 했습니다.

저는 기존에 사용해오던 useMedia hooks를 통해서 해당 컴포넌트에 mediaStream 을 연결할 수 있었습니다.

2. createMicrophoneVolumeMonitor 에 대한 기능 소개

microphone Volume 에 대한 데이터를 측정해야만 했는데요!
입력받은 stream 에서 audio 데이터를 받아서 처리하는 과정이 필요했습니다.

주의해야할 점이, 마이크의 볼륨의 변화를 감지하여 코드에 반환하는것이 아닌,
현재 마이크의 볼륨을 "관측"하는 것입니다. 그래서 monitoring 이라는 함수명을 사용했습니다.

export const createMicrophoneVolumeMonitor = (
  stream: MediaStream, // 마이크 입력 스트림
  volumeCallback: React.Dispatch<React.SetStateAction<number>> // 볼륨 업데이트 콜백
) => {
  // 오디오 컨텍스트를 생성하고 입력 스트림을 오디오 컨텍스트에 연결합니다.
  const audioContext = new AudioContext();
  const sourceNode = audioContext.createMediaStreamSource(stream);
  const analyser = audioContext.createAnalyser();

  // FFT 사이즈를 설정합니다. 이 값은 PCM 데이터의 길이를 결정합니다.
  analyser.fftSize = 2048;

  // 소스 노드를 분석기에 연결하고 PCM 데이터를 저장할 배열을 생성합니다.
  sourceNode.connect(analyser);
  const pcmData = new Float32Array(analyser.fftSize);

  let intervalId: NodeJS.Timer | number;

  // 모니터링을 시작하는 함수를 정의합니다.
  const startMonitoring = () => {
    // 주기적으로 볼륨을 체크하기 위한 인터벌을 설정합니다. -> 마이크 입력에 따라 해당 함수가 호출되는것이 아닌, 주기적으로 현재 마이크 상태를 반환하는것
    intervalId = setInterval(() => {
      analyser.getFloatTimeDomainData(pcmData);

      // PCM 데이터를 통해 제곱합을 계산합니다.
      const sumSquares = pcmData.reduce((sum, value) => sum + value * value, 0);
      const rms = Math.sqrt(sumSquares / pcmData.length);

      // 볼륨을 증폭하기 위한 가중치를 설정합니다.
      const weight = 7;
      const amplifiedVolume = rms * weight;

      // 볼륨을 0에서 100 사이로 정규화합니다.
      const normalizedVolume = Math.min(Math.round(amplifiedVolume * 100), 100);

      // 볼륨을 업데이트 하는 setState 함수를 실행시킵니다.
      volumeCallback(normalizedVolume);
    }, 150);
  };

  // 모니터링을 중지하는 함수를 정의합니다.
  const stopMonitoring = () => {
    // 인터벌 타이머가 설정되어 있으면 해제합니다.
    if (intervalId) {
      clearInterval(intervalId as number);
    }
    sourceNode.disconnect();
    analyser.disconnect();
    void audioContext.close();
  };

  return { startMonitoring, stopMonitoring };
};

다음과 같이 microphone에 대한 monitoring을 진해알 수 있는 util 함수를 구현하여 VolumeStatus 컴포넌트의 비지니스 로직에서 대처할 수 있도록 했습니다.

3. callback으로 넘겼던 함수를 통해서 audiovolume 에 대해서 처리

해당 audioBolume에 return 값을 callback함수로 넘겼던 setState 를 통해서 값을 갱신합니다. 이제 해당 갱신된 값을 바탕으로 동적으로 Audio 의 UI를 변경해주어야 합니다.

이때 두가지 초점을 두었습니다.

1. 모두 동일한 Audio의 크기를 나타내지 말자

aduioVolume을 기준으로 조금씩은 다른 높이를 제공하고 싶었습니다.

2. 매 호출 마다 다른 UI 를 제공하자.

다음 코드를 통해서 매번 다른 색의 UI를 제공할 수 있었으며, 우리 서비스 메인 컬러를 기준으로 변경될 수 있었습니다.

Result

구현된 결과 입니다. 마이크의 입력 볼륨에 따라서 동적으로 heigh가 변경되며, 크게 어렵지 않게 해결할 수 있었습니다.

곰터뷰 에서 확인해보실 수 있습니다.

post-custom-banner

0개의 댓글