오늘 한 일
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
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>
실행 화면
'항해 중 > 8-13주차 실전 프로젝트' 카테고리의 다른 글
[실전 프로젝트] 다이어리 페이지 마무리, 네비게이션 바 만들기 (0) | 2021.12.29 |
---|---|
[실전 프로젝트] 달력 + 해당 월의 일수 계산하기 (+ fill : 특정 값으로 배열 채우기) (0) | 2021.12.28 |
[실전 프로젝트] 푸쉬 알람 여부 (0) | 2021.12.27 |
[실전 프로젝트] API 설계, 기능 자료 조사, 프로젝트 기본 틀 잡기 (0) | 2021.12.24 |
[실전 프로젝트] UX Flow / 와이어 프레임 / API (0) | 2021.12.23 |
댓글