밍경송의 E.B
[졸업프로젝트] 리액트- 텍스트를 MP3파일로 변환하고 이를 순차적으로 재생하는 기능의 구현 본문
졸업 프로젝트로 <DISLODGED>라는 보이스 위로 전달 감성 웹서비스를 기획 및 개발하게 됐습니다.
저는 기획/디자인 및 프론트로 참여하였고, 리액트를 이용해 개발을 진행했습니다.
이번 글에서는 텍스트 댓글을 댓글 작성자의 가상보이스와 합성하여 MP3 파일 형태로 만들고, 이것을 유저에게 순차적으로 재생해주는 기능을 구현하는 방법에 대해 다루고자 합니다.
자세한 서비스 내용은 생략하겠습니다.
목표 : 유저가 작성한 게시글을 음반처럼 보여주고 보이스 댓글을 음악처럼 들려주기
감성 웹 서비스인 만큼, 디자인에 신경을 더 쓰려고 노력했습니다.
<백엔드와의 소통>
청각과 관련이 깊은 서비스인 만큼, 게시글 하나를 LP판처럼 나타내고 싶어서! 저작권이 없는 감성적인 이미지 약 10개를 찾아서 백에 넘겨주고 이미지 URL을 응답으로 넘겨받도록 했습니다.
그리고 백 팀원에게 유저 경험 향상을 위해 이미지가 최대한 중복되지 않게 해달라고 의견을 전달했습니다.
이미지를 게시글마다 보여주기 위한 로직과 코드는 바로 아래에서 자세히 설명하겠습니다.
우선, 위와 같은 페이지 구현을 위해 필요한 파일 구조(연쇄적으로 호출하는 구조)는 다음과 같습니다.
Mypage.jsx → MypageList.jsx → MypageItem.jsx
각 요소들에 대해 자세히 알아보겠습니다.
Mypage.jsx - 마이페이지의 전체적인 관리 역할
1) get api를 이용해 내가 작성한 게시물 불러오는 API 함수를 작성합니다.
api.js
//마이페이지 - 내가 작성한 게시글 불러오는 함수
export const getMyForumPosts = async (page) => {
try {
const response = await client.get(`/accounts/mypost/?page=${page}`);
const postWithComments = response.data.results.map((post) => ({
...post,
// API 응답에 댓글 수가 포함되어 있다면 직접 사용하고,
// 그렇지 않은 경우 comment 배열의 길이 등으로 계산
commentCount: post.comment?.length || 0
}));
return {
postWithComments,
totalCount: response.data.total // 전체 포스트 수
};
} catch (error) {
console.error("내가 작성한 게시물 목록을 가져오는 데 실패했습니다:", error);
throw error;
}
};
2) getMyForumPosts 함수를 호출하여 서버에서 내가 작성한 게시글들을 마이페이지로 가져옵니다.
Mypage.jsx
useEffect(() => {
const getMyPostList = async () => {
try {
const { postWithComments, totalCount } = await getMyForumPosts(page);
setPostList(postWithComments);
setPageCount(Math.ceil(totalCount / 4));
} catch (error) {
console.error("Failed to retrieve my forum posts:", error);
}
};
getMyPostList();
}, [searchParams, page]);
3) 가져온 PostList(게시글 리스트)를 MypageList.jsx로 넘겨줍니다.
<MypageList mypageList={postList} />
{/*여기서 postList는 상태 변수임 */}
MypageList.jsx - 마이페이지 내의 Item 간의 거리 등의 관리 역할
*서버로부터 받아 온 응답의 PostList(게시물 리스트) 중 하나의 게시물에 대한 응답의 구조는 아래와 같습니다.
{ id: "2d0e793d-1002-400f-b706-e47204b074cd"
author_id: "96599e26-c114-460e-b7de-7a4b6e89d095"
author_nickname: "유저 닉네임"
comment: []
content: "글 내용"
created_at: "2024-05-03T22:34:39.793940+09:00"
group: "가족/친구"
id: "2d0e793d-1002-400f-b706-e47204b074cd"
image_url: "https://dislodged.s3.ap-northeast-2.amazonaws.com/image/Frame+1.png"
tag: "음반 위에 보여질 태그"
title: "제목"
updated_at: "2024-05-03T22:34:39.793970+09:00"
}
→ 이 중 음반 구성을 위해 필요한 것은 : 해당 글의 id, 음반 위의 제목으로 보여질 글의 tag, 서버 db에 미리 저장해둔 이미지 url, 해당 글에 달린 댓글들 정도입니다.
따라서 MypageList에서는 응답 중 필요한 요소들(id, tag, picture, comments)만 MypageItem.jsx로 넘겨줍니다.
MypageList.jsx
const MypageList = ({ mypageList }) => {
return (
<div className="mypage-body">
{mypageList.length > 0 ? (
mypageList.map((post) => (
<MypageItem
id={post.id}
tag={post.tag}
picture={post.image_url}
comments ={post.comment}
/>
))
) : (
<p className="no-posts"> 작성한 게시글이 없습니다.</p>
)}
</div>
);
};
MypageItem.jsx- 각 item(게시물 1개)을 관리하는 역할
MypageItem.jsx
const MypageItem = ({ id, tag, picture, comments }) => {
//MypageList에서 넘어온 id, tag, picture, comments를 이용해 Item 별 로직 구현
};
이때 item(게시글마다의 음반)에 구현해야 하는 부분은 크게 2가지 입니다.
[ 1 ] tag를 원 음반 위에 제목에, picture를 원 안에 넣어 트랙의 형태 구현하기
→ 이 부분의 경우 간단 CSS로 구현 가능하기 때문에 부연 설명은 생략하겠습니다.
[ 2 ] 음반(사진)을 눌렀을 때의 로직 구현하기
먼저, 해당 게시글의 댓글 유무에 따라 다른 로직을 구현하려고 했습니다.
만약 해당 게시글에 댓글이 존재한다면, 음반(사진)을 눌렀을 때 아래 오른쪽 그림처럼 2개의 버튼이 뜨게 합니다.
음원 재생을 위해서는 모든 댓글을 음성으로 변환 및 저장하는 과정[음원 불러오기] 와 저장한 음원을 재생하는 과정[음원 재생하기] 과정이 필요한데요.
먼저, post api 함수를 작성하여 내가 선택한 게시물의 댓글들을 mp3 파일로 변환하는 과정을 거치도록 합니다.
api.js
//마이페이지에서 게시글마다의 댓글을 음성으로 변환 및 저장하는 api
export const postMyTracklist = async (postId) => {
try {
const response = await client.post(`/posts/Mp3File/${postId}/`);
return response.data; // 성공적으로 댓글을 추가한 후의 데이터 반환
} catch (error) {
console.error("댓글 음성 변환에 실패했습니다.", error);
throw error;
}
};
그리고 사용자가 [음원 불러오기] 버튼을 눌렀을 때 postMyTrackList 함수를 실행하여 서버에 댓글을 전송하도록 합니다.
MypageItem.jsx
const initiatePlayList = async () => {
setIsLoading(true);
try {
await postMyTracklist(id);
setIsClicked(true);
} catch (err) {
setError(err.message);
} finally {
setIsLoading(false);
}
};
post api를 호출하는 동시에, 버튼 위 글씨가 [음원 불러오기] 에서 [음원 재생하기]로 변경되도록 코드를 구성했습니다.
이후 사용자가 [음원 재생하기] 버튼을 누르면,
get api를 사용해서 전에 post api를 통해 변환 및 저장됐던 댓글 mp3 파일들을 서버에서 응답으로 가져오도록 구현했는데요.
api.js
//마이페에지에서 보이스 댓글 url 쭉 받아오는 api
export const getMyTracklist = async (postId) => {
try {
const response = await client.get(`/posts/Mp3File/${postId}/`);
return response.data;
} catch (error) {
console.error("댓글 음성을 불러오는 데 실패했습니다.", error);
throw error;
}
};
이때 getMyTracklist 함수를 이용해 서버에서 넘어온 응답의 구조는 다음과 같습니다.
{
RESULT:
["https://dislodged.s3.ap-northeast-2.amazonaws.com/mp3/12b729d3-2fb0-492a-9166-d10533ef2295/0.mp3",…]
0: "https://dislodged.s3.ap-northeast-2.amazonaws.com/mp3/12b729d3-2fb0-492a-9166-d10533ef2295/0.mp3"
1: "https://dislodged.s3.ap-northeast-2.amazonaws.com/mp3/12b729d3-2fb0-492a-9166-d10533ef2295/1.mp3"
2: "https://dislodged.s3.ap-northeast-2.amazonaws.com/mp3/12b729d3-2fb0-492a-9166-d10533ef2295/2.mp3"
3: "https://dislodged.s3.ap-northeast-2.amazonaws.com/mp3/12b729d3-2fb0-492a-9166-d10533ef2295/3.mp3"
4: "https://dislodged.s3.ap-northeast-2.amazonaws.com/mp3/12b729d3-2fb0-492a-9166-d10533ef2295/4.mp3"
5: "https://dislodged.s3.ap-northeast-2.amazonaws.com/mp3/12b729d3-2fb0-492a-9166-d10533ef2295/5.mp3"
6: "https://dislodged.s3.ap-northeast-2.amazonaws.com/mp3/12b729d3-2fb0-492a-9166-d10533ef2295/6.mp3"
7: "https://dislodged.s3.ap-northeast-2.amazonaws.com/mp3/12b729d3-2fb0-492a-9166-d10533ef2295/7.mp3"
}
여기서 제가 해야 할 부분은, RESULT로 넘어 온 mp3 파일들을 순차적으로 재생해야 한다는 것이었습니다.
따라서 저는 아래와 같은 로직의 코드를 구현해봤습니다.
1) 응답의 RESULT를 상태 변수 tracklist에 설정하고, playAudio(0)를 호출하여 1번째 트랙을 재생하도록 한다.
2) 이때 playAudio()는 조건문을 통해 마지막 인덱스의 트랙까지 트랙을 순차적으로 재생하도록 한다.
구현한 코드는 아래와 같습니다.
const playTrackList = async () => {
try {
const data = await getMyTracklist(id);
setTracklist(data.RESULT);
if (data.RESULT.length > 0) {
playAudio(0);
setButtonText("음원 재생하기");
}
} catch (err) {
setError(err.message);
}
};
const playAudio = (trackIndex) => {
const audio = new Audio(tracklist[trackIndex]);
audio.play();
audio.onended = () => {
if (trackIndex < tracklist.length - 1) {
playAudio(trackIndex + 1);
}
};
};
그러니까 정리해보면,
playTrackList 함수는 버튼의 글씨를 변경해주고, 트랙 리스트를 가져와 첫 번째 트랙을 재생하하는 함수이고, 이에 따라 호출된 playAudio 함수는 재생 중인 트랙이 끝나면 다음 트랙을 재생하는 함수입니다.
이렇게 하면, 각 게시글마다의 보이스 댓글들을 마치 음악처럼 쭉 들을 수 있게 됩니다.
아까 위에서, 해당 게시글의 댓글 유무에 따라 로직을 달리한다는 말씀을 드렸었는데요.
댓글이 있으면 버튼 2개를 생성하게 하는 한편,
댓글이 없으면 불러오고 재생할 음원이 없기 때문에 해당 게시글로 이동하도록 구현했습니다.
또한, 음반을 눌렀을 때의 2번째 버튼인 [글 보러가기 버튼]을 눌러도 해당 게시글로 이동하도록 했습니다.
이때 MypageList로부터 넘어 온 게시글의 id 값을 url의 끝부분에 붙이는 방식으로 코드를 구현할 수 있었습니다.
MypageItem.jsx
const goToArticle = () => {
navigate(`/detail/${id}`);
};
이렇게 코드를 입력하고, CSS를 이용해 원하는 스타일로 요소들을 꾸며 해당 기능의 구현을 마무리할 수 있었습니다.
'CSE > 캡스톤디자인' 카테고리의 다른 글
End-to-End TTS(Text-To-Speech)의 이해 (0) | 2023.11.24 |
---|