[실전 프로젝트] 녹음, 녹음 파일 재생 + 보이스 페이지..?
본문 바로가기
항해 중/8-13주차 실전 프로젝트

[실전 프로젝트] 녹음, 녹음 파일 재생 + 보이스 페이지..?

by 은돌1113 2021. 12. 28.

오늘 한 일

1. 어제 각자 진행한 부분 develop branch에 merge 함

2. API에 맞춰서 api.js 수정하고 instanceRecord.js 추가함

3. 녹음 기능 맡아서 진행 (어제 연습 했던 거 참고해서 구현 하면 될 것 같다!)

4. 팀장 멘토링에서 보이스 페이지는 빼는 게 좋을 것 같다는 피드백을 받아서 보이스 페이지는 추후에 구현하는 걸로 미뤄짐

5. 다이어리 페이지에 달력 구현 맡아서 진행하기로 함 (해당 과정은 다음 게시물에서 정리함)

 

기능 구현

1. src/pages/Voice.js를 구현 하던 중 버블 차트와 음성 녹음 컴포넌트를 분리 하는 게 좋을 것 같다는 생각이 들었다.

→ src/components/Bubble.js와 src/components/AudioRecord.js를 새로 만들어서 구현 하였다.

 

2. Bubble.js 

 

1) 다른 사용자가 녹음한 파일을 들을 경우 ▶ → ■로 변경 해주기 위해서 아래 코드를 작성 하였다.

document.getElementById(index+"item").innerHTML = "■";
  // ** 사용자가 누른 div만 정해진 색상으로 활성화 **
  const changeColor3 = (index) => {
    if (checkCircle === "") {
      // 전에 눌렀던 게 없을 때
      let x = document.getElementById(index);
      x.style.backgroundColor = "purple";
      checkCircle = x;
    } else if (parseInt(checkCircle.id) === index) {
      console.log("눌렀던 거 또 누름");
    } else {
      // 전에 눌렀던 음성이 있으면 다시 비활성화
      checkCircle.style.backgroundColor = "grey";
      let x = document.getElementById(index);
      checkCircle = x; // 바꿔치기
      checkCircle.style.backgroundColor = "purple"; // 정해진 색상으로만 활성화 되게
    }

    document.getElementById(index+"item").innerHTML = "■";
  };
  
  // =============================================================
  
<div
  id={`${index}item`}
    style={{
      width: `${item}px`,
      height: `${item}px`,
      lineHeight: `${item}px`,
    }}
  >
     ▶
</div>

https://ponyozzang.tistory.com/685

 

자바스크립트 HTML 표시내용 변경 innerHTML 사용 방법

자바스크립트로 무언가 처리를 하고 그 결과에 따라 화면상에 문자열을 변경하고 싶은 경우가 있습니다. 이런 경우에는 innerHTML 프로퍼티를 사용해 표시하고 싶은 내용으로 변경할 수 있습니다.

ponyozzang.tistory.com

 

2) 새로고침 버튼을 만들어 줬다.

<button onClick={()=>{window.location.reload()}}>새로고침</button>

 

3) 백엔드 분들이 audio API 완성 해주시면 버블을 눌렀을 때 음성이 나오는 지 확인 해봐야 한다. 

 

더보기
더보기
더보기

 

import React from "react";
import { useSelector } from "react-redux";

const Bubble = (props) => {
  const style = { area: [], gap: [] }; // div들을 담고 있는 객체
  let checkCircle = ""; // 현재 재생 중인 음성의 index가 무엇인 지 담는다.
  const voiceInfo = useSelector((state) => state.voice.voiceInfo);

  for (let i = 0; i < 10; i++) {
    style.area.push(Math.ceil(Math.random() * (100 - 40) + 40)); // 동그라미
    style.gap.push(Math.ceil(Math.random() * (10 - 5) + 5)); // 간격
  }

  // ** 사용자가 누른 div만 정해진 색상으로 활성화 **
  const changeColor3 = (index, voice) => {
    if (checkCircle === "") {
      // 전에 눌렀던 게 없을 때
      let x = document.getElementById(index);
      x.style.backgroundColor = "purple";
      checkCircle = x;
    } else if (parseInt(checkCircle.id) === index) {
      console.log("눌렀던 거 또 누름");
    } else {
      // 전에 눌렀던 음성이 있으면 다시 비활성화
      checkCircle.style.backgroundColor = "grey";
      let x = document.getElementById(index);
      checkCircle = x; // 바꿔치기
      checkCircle.style.backgroundColor = "purple"; // 정해진 색상으로만 활성화 되게
    }

    document.getElementById(index + "item").innerHTML = "■";
    play(voice);
  };

  const play = (voice) => {
    const audio = new Audio(URL.createObjectURL(voice));
    audio.loop = false;
    audio.volume = 1;
    audio.play();
  };

  return (
    <div>
      <button onClick={()=>{window.location.reload()}}>새로고침</button>
      <div
        style={{
          width: "23%",
          flexWrap: "wrap",
          display: "flex",
          justifyContent: "center",
          alignItems: "center",
        }}
      >
        {style.area.map((item, index) => {
          return (
            <p
              key={item}
              id={index}
              style={{
                width: `${item}px`,
                height: `${item}px`,
                backgroundColor: "grey",
                borderRadius: "50%",
                marginRight: `${style.gap[index]}px`,
                marginBottom: `-${style.gap[index]}px`,
              }}
              onClick={() => {
                changeColor3(index, voiceInfo[index]);
              }}
            >
              <div
                id={`${index}item`}
                style={{
                  width: `${item}px`,
                  height: `${item}px`,
                  lineHeight: `${item}px`,
                }}
              >
                ▶
              </div>
            </p>
          );
        })}
      </div>
    </div>
  );
};

export default Bubble;

 

3. AudioRecord.js

: script 부분에서 오디오 부분과 타이머 부분이 나뉜다.

 

1) 오디오 부분

  const [stream, setStream] = useState();
  const [media, setMedia] = useState();
  const [onRec, setOnRec] = useState(true);
  const [source, setSource] = useState();
  const [analyser, setAnalyser] = useState();
  const [audioUrl, setAudioUrl] = useState();
  const [sound, setSound] = useState();
  
  // -----------------------------------------
  
   const onRecAudio = () => {
    setCount(0); // 재녹음 시 타이머 초기화
    start(); // 타이머 시작
    setDisabled(true);

    // 음원정보를 담은 노드를 생성하거나 음원을 실행또는 디코딩 시키는 일을 한다
    const audioCtx = new (window.AudioContext || window.webkitAudioContext)();
    // 자바스크립트를 통해 음원의 진행상태에 직접접근에 사용된다.
    const analyser = audioCtx.createScriptProcessor(0, 1, 1);
    setAnalyser(analyser);

    function makeSound(stream) {
      // 내 컴퓨터의 마이크나 다른 소스를 통해 발생한 오디오 스트림의 정보를 보여준다.
      const source = audioCtx.createMediaStreamSource(stream);
      setSource(source);
      source.connect(analyser);
      analyser.connect(audioCtx.destination);
    }
    // 마이크 사용 권한 획득
    navigator.mediaDevices.getUserMedia({ audio: true }).then((stream) => {
      const mediaRecorder = new MediaRecorder(stream);
      mediaRecorder.start();
      setStream(stream);
      setMedia(mediaRecorder);
      makeSound(stream);

      analyser.onaudioprocess = function (e) {
        // 3분(180초) 지나면 자동으로 음성 저장 및 녹음 중지
        if (e.playbackTime > 180) {
          stream.getAudioTracks().forEach(function (track) {
            track.stop();
          });
          mediaRecorder.stop();
          // 메서드가 호출 된 노드 연결 해제
          analyser.disconnect();
          audioCtx.createMediaStreamSource(stream).disconnect();

          mediaRecorder.ondataavailable = function (e) {
            setAudioUrl(e.data);
            setOnRec(true);
          };
        } else {
          setOnRec(false);
        }
      };
    });
  };

  // 사용자가 음성 녹음을 중지 했을 때
  const offRecAudio = () => {
    end();

    // dataavailable 이벤트로 Blob 데이터에 대한 응답을 받을 수 있음
    media.ondataavailable = function (e) {
      setAudioUrl(e.data);
      setOnRec(true);
    };

    // 모든 트랙에서 stop()을 호출해 오디오 스트림을 정지
    stream.getAudioTracks().forEach(function (track) {
      track.stop();
    });

    // 미디어 캡처 중지
    media.stop();

    // 메서드가 호출 된 노드 연결 해제
    analyser.disconnect();
    source.disconnect();

    if (audioUrl) {
      URL.createObjectURL(audioUrl); // 출력된 링크에서 녹음된 오디오 확인 가능
    }

    // File 생성자를 사용해 파일로 변환
    const sound = new File([audioUrl], "soundBlob", {
      lastModified: new Date().getTime(),
      type: "audio",
    });

    setDisabled(false);
    setSound(sound); // File 정보 출력
  };

  const play = () => {
    const audio = new Audio(URL.createObjectURL(audioUrl));
    audio.loop = false;
    audio.volume = 1;
    audio.play();
  };

  const dispatch = useDispatch();

  const send = () => {
    setCount(0);
    dispatch(voiceActions.voiceRecordDB(sound));
  };
  
   // -----------------------------------------
  
<div>
  <button onClick={play} disabled={disabled}>
    재생
  </button>
  <button onClick={onRec ? onRecAudio : offRecAudio}>녹음</button>
  <button onClick={send} disabled={disabled}>
    전송
  </button>
 </div>

 

2) 타이머 부분

  const [count, setCount] = useState(0);
  const [currentMinutes, setCurrentMinutes] = useState(0);
  const [currentSeconds, setCurrentSeconds] = useState(0);
  const intervalRef = useRef(null);
  
  // -----------------------------------------
  
    const start = () => {
    intervalRef.current = setInterval(async () => {
      setCount((c) => c + 1);
    }, 1000);
  };

  const end = () => {
    clearInterval(intervalRef.current);
  };

  const timer = () => {
    const checkMinutes = Math.floor(count / 60);
    const minutes = checkMinutes & 60;
    const seconds = count % 60;

    setCurrentMinutes(minutes);
    setCurrentSeconds(seconds);
  };

  // count의 변화에 따라 timer 함수 렌더링
  useEffect(timer, [count]);
  
    // -----------------------------------------
    
 <h1>
   {currentMinutes < 10 ? `0${currentMinutes}` : currentMinutes} :{" "}
   {currentSeconds < 10 ? `0${currentSeconds}` : currentSeconds}
 </h1>

 

실행 화면

댓글