[DISMU] WebSocket을 활용한 공용 플레이리스트 구현 (nest, next)
👋 소개
안녕하세요! 대학생 개발자 주이어입니다!
오늘은 글을 진짜 오랜만에 올리게 되었는데요. 최근에 여러 프로젝트를 제작하면서 개인 공부에 집중하다 보니 블로그 작성하는 주기가 조금 길어지게 되었습니다.
그래서 오늘은 제가 최근에 진행중인 DISMU 프로젝트에 대해서 간단하게 소개하고,
프로젝트를 제작하면서 사용된 기능중 하나인 "websocket을 활용한 공용 플레이리스트"에 대해서 정리해보려고 합니다.
그럼 바로 시작하도록 하겠습니다.
💻 프로젝트 소개
제가 현재 진행하고 있는 DISMU 프로젝트는 discord와 music을 합친 단어로, 디스코드 노래봇과 관련된 프로젝트입니다.
디스코드를 사용하신다면 아마 한 번 쯤은 노래봇을 사용해보지 않았을까 싶은데요.
물론 노래봇이 디스코드의 핵심 기능은 아니지만, 자주 사용하는 유저들에게는 몇 가지 불편한 점이 있었습니다.
1. 텍스트 UI의 한계
디스코드 봇으로 친구들과 같이 노래를 들을 수 있다는 건 정말 좋지만,
텍스트로만 플레이리스트를 조작하고, 노래를 검색하는 것은 이용에 있어서 꽤 불편한 부분을 차지하였습니다.
저는 이 부분에서 웹사이트와 같은 인터페이스가 있으면 좋겠다고 생각하였습니다.
2. 플레이리스트 조작의 한계
텍스트 UI와 이어지는 부분이지만, 플레이리스트를 한 눈에 보기 어렵고, 또 중간에 수정하기가 굉장히 제한적이라는 점에서 불편한 점이 있었습니다.
또한 재생목록을 추가할 경우, 노래가 한 번에 100곡 씩 등록되는 문제가 있기도 했습니다.
3. 사용자 노래 추가
이 부분은 크게 불편한 점까지는 아니었지만, 가끔 직접 추가한 노래를 듣고 싶다는 생각을 한 적이 있었고,
이를 프로젝트에 반영하고자 했습니다.
위와 같은 불편한 점 뿐만 아니라, 아래와 같은 목표를 이루기 위해서 이번 프로젝트를 기획하였습니다.
AI 노래 플랫폼
요즘 AI노래가 유행하면서 유튜브에서도 꽤 많이 보이는데요.
심지어 AI노래를 전문적으로 만들어서 공유하는 유튜버도 생겼습니다.
하지만 아직 이런 AI 노래를 저작권과 함께 보호하고, 공유하며, 감상할 수 있는 플랫폼은 존재하지 않아 보였습니다.
그래서 저는 이번 프로젝트에 AI 노래의 플랫폼 기능까지 넣고 싶다는 생각을 하였습니다.
위와 같은 점들을 토대로 이번 프로젝트를 기획하고 제작하게 되었습니다.
그래서 이 프로젝트를 한 줄로 설명하자면, "웹사이트 인터페이스를 기반으로 플레이리스트를 수정하고, 이를 디스코드 봇으로 들을 수 있는 프로젝트" 입니다.
⚠️ 문제점 설명
일반적인 디스코드 노래 봇을 보면 알 수 있듯이 같은 길드에 있는 여러 사용자가 플레이리스트를 수정할 수 있습니다.
봇으로 만든다면 그렇게 어려운 기능 구현은 아니었지만, 이를 웹사이트 플레이리스트로 제작한다면,
여러 사용자가 실시간으로 통신하는 것을 구현해야 했습니다.
이전에 진행했던 다른 프로젝트에서 단일 사용자가 서버와 websocket을 통해 실시간으로 통신하는 것은 구현한 적이 있었지만, 여러 사용자가 동시에 통신하는 것은 이번이 처음 구현해보는 작업이었습니다.
또한 디스코드 길드마다 플레이리스트가 다르고 사용자도 다르기 때문에, 이에 맞춰 여러 통신 서버를 구축해야했습니다.
이를 구현하기 위해서 ai,구글링 등을 활용하였으며, 밑에서 이러한 기능을 어떻게 구현했는지 순서대로 설명할 예정입니다.
🌐 디스코드 길드별 통신 서버 나누기
제가 찾은 방법은 socket 기능 중 하나인 join을 이용하는 것 이었습니다.
join은 특정 id를 기준으로 사용자를 그룹화하여, 요청에 따라 해당 그룹(서버)으로 연결되도록 하는 기능입니다.
저는 이 id를 길드 id 기준으로 나누도록 설정하였고, 이렇게 하면 해당 길드 플레이리스트를 이용 중인 사람들은 같은 socket 서버에 연결되도록 할 수 있었습니다.
@SubscribeMessage('joinGuild')
handleJoinGuild(
@ConnectedSocket() client: Socket,
@MessageBody() payload: { guildId: string },
) {
if (!payload.guildId) return;
client.join(payload.guildId);
client.emit('joined', `Joined guild ${payload.guildId}`);
}
위는 제 프로젝트에 사용된 nest 코드입니다.
위에서 설명했듯이 join과 guildId를 사용하여 서버를 나누도록 만들었습니다.
추가로 SubscribeMessage는 백엔드 API route와 같이 클라이언트 요청을 분리 처리 해주는 역할을 합니다.
ConnectedScoket은 요청을 보내 사용자의 서버를 의미하고,
MessageBody는 사용자가 요청과 함께 보낸 정보를 읽을 때 사용합니다.
🎧 노래 추가 실시간 연동
같은 길드 플레이리스트를 이용 중인 사람들을 같은 서버에 연결시켰으니,
이제 실시간으로 UI가 바뀌고 서로의 작업이 공유되도록 만들어줘야 합니다.
이를 해결하기 위해서 제가 생각한 방법은 특정 사용자가 노래를 추가할 경우 socket 서버에 신호를 보내고,
socket서버에서 해당 길드에 연결된 모든 사용자에게 추가된 노래 정보와 함께 노래가 추가되었다는 알림을 보내는 방법이었습니다.
next에서는 전달받은 노래 정보를 상태 변수에 적용하여 다시 렌더링 되도록 하면 실시간 연동이 가능할 것 같았습니다.
@WebSocketServer()
server: Server;
먼저 여러 사용자에게 알림을 보내기 위해서 Server 객체를 주입시켜 주었습니다.
nest에서는 @WebSocketServer 데코레이터가 붙은 필드에 자동으로 Server 객체를 주입해주기 때문에,
위와 같이 간단하게 사용할 수 있었습니다.
@SubscribeMessage('addSong')
handleAddSong(@MessageBody() data: PLSong) {
const { guildId } = data;
this.server.to(guildId).emit('songAdded', data);
}
주입받은 Server 객체를 사용하면 this.server를 통해 서버에 연결된 모든 사용자에게 알림을 보낼 수 있습니다.
저는 그 서버 중에서도 특정 서버에 알림을 보내야 했기 때문에 .to를 사용하여 어떤 서버에 알림을 보낼지 설정해주었고,
이 기준은 당연히 사용자가 노래를 추가한 길드 id를 기준으로 하였습니다.
위의 짧은 코드로 nest서버가 해야할 역할은 끝났습니다.
이제 next에서 알림을 받고 이를 활용해 새로 렌더링해야 했습니다.
useEffect(() => {
if (!curGuild) return;
socket.current = io(`${process.env.NEXT_PUBLIC_BACKEND}`);
socket.current.emit("joinGuild", { guildId: curGuild.id });
socket.current.on("joined", (msg: string) => {
console.log(msg);
});
socket.current.off("songEnd");
socket.current.on("songEnd", (msg: string) => {
console.log(msg);
setQueue((prev) => [...prev.slice(1, prev.length)]);
});
socket.current.off("songAdded");
socket.current.on("songAdded", (song: PLSong) => {
console.log("songAdded");
setQueue((prev) => [...prev, song]);
});
return () => {
socket.current?.disconnect();
socket.current = null;
};
}, [curGuild]);
먼저 저는 위와 같이 코드를 구현하였습니다.
playlist 컴포넌트에 useEffect를 사용하여 socket 서버에 연결되도록 설정하였고,
curGuild 상태 변수를 사용하여, 선택한 길드가 변경될 경우에 서버도 그에 맞춰 다시 연결되도록 하였습니다.
songAdded를 사용하여 서버에서 전달한 노래 정보를 받도록 설정하였고,
플레이리스트 정보를 가지고 있는 상태 변수인 queue에 추가하여 렌더링에 반영되도록 하였습니다.
여기까지 해주면 제가 계획한대로,
사용자 노래 추가 -> socket에서 처리 후 전체 알림 -> 각 클라이언트에서 렌더링 반영
구조까지 완료되었습니다.
🎧 노래 종료 실시간 연동
노래 종료도 노래 추가와 같이 구현하면 될 것 같지만, 한 가지 문제가 있었습니다.
바로 노래 재생이 웹사이트에서 진행되는 것이 아닌, 디스코드 봇에서 진행된다는 점입니다.
또한 저는 디스코드 봇을 python으로 제작 중이었기 때문에, nest에서도 노래가 종료되었는지 알 방법이 없었습니다.
그래서 저는 python에서 노래가 끝났을 때, nest로 api요청을 보내고,
nest에서는 이 요청을 gateway(socket 서버)에서 처리할 수 있도록 하였습니다.
@Post('songEnd')
async songEnd(@Body() body: { guildId: string }) {
const { guildId } = body;
if (!guildId) return { error: 'No GuildId' };
await this.playlistService.songEnd(guildId);
this.playlistGateway.songEnd(guildId);
return { success: true };
}
먼저 api 요청을 받을 수 있도록, controller에서 위와 같이 songEnd api를 제작해주었습니다.
그리고 데이터베이스 관련 작업을 처리해줄 service와
실시간 통신 알림을 보낼 gateway와 연결해주었습니다.
async songEnd(guildId: string) {
const playlist = await this.prisma.playlist.findUnique({
where: { guildId },
include: { PlaylistSongs: { orderBy: { id: 'asc' }, take: 1 } },
});
if (!playlist || playlist.PlaylistSongs.length == 0) {
throw new Error('No song in playlist');
}
const firstSong = playlist.PlaylistSongs[0];
await this.prisma.playlistSong.delete({
where: { id: firstSong.id },
});
return firstSong;
}
service에서는 위와 같이 prisma ORM을 사용하여 플레이리스트 데이터베이스를 수정하도록 만들어주었습니다.
기본적으로 노래가 종료되었다면 가장 먼저 추가되었던 노래가 종료되었다는 뜻이기에
가장 첫번째로 들어온 데이터를 삭제하도록 하였습니다.
songEnd(guildId: string) {
this.server.to(guildId).emit('songEnd', 'songEnd');
}
gateway에서는 사용자에게 노래가 끝났다는 알림만 보내면 되기 때문에 위와 같이 간단하게 만들어 주었습니다.
이렇게 해주면 python에서 노래가 끝났다는 요청이 왔을 때, 데이터베이스를 처리하고, 서버 통신도 처리할 수 있게 됩니다.
😊 결과 및 마무리
위 사진만으로는 확인하기 쉽지 않지만, 2개의 기기를 두고 테스트 해본 결과 노래 추가와 제거 모두 실시간으로 잘 연동이 되며,
동시에 여러 사용자가 플레이리스트를 조작하는데 있어서 문제가 발견되지 않았습니다.
또한 디스코드 봇에서 노래도 잘 재생되었습니다.
오늘 이렇게 여러 사용자가 동시에 실시간 통신을 해야되는 경우에 어떻게 구현해야하는지 정리해보았고,
플레이리스트 로직에 대해서 간단하게 정리해보았습니다.
websocket과 실시간 통신은 생각보다 자주 사용되는 기능인데 이번 기회에 자세히 공부하고 응용해본 것 같고,
단순한 기능 구현을 넘어서, 네트워크 관련 지식까지 함께 쌓을 수 있었던 의미 있는 시간이었습니다.
그럼 지금까지 읽어주셔서 감사드리고, 다음에는 더 좋은 내용의 글로 찾아오도록 하겠습니다.
혹시 저와 같이 소통하고 프로젝트를 만들어보고 싶다면 아래 링크로 들어와주세요!
KYT CODING COMMUNITY Discord 서버에 가입하세요!
Discord에서 KYT CODING COMMUNITY 커뮤니티를 확인하세요. 25명과 어울리며 무료 음성 및 텍스트 채팅을 즐기세요.
discord.com
KYT CODING COMMUNITY 가입하기!