Skip to content

Commit

Permalink
Merge pull request #303 from platinouss/refactor/240114-media-server
Browse files Browse the repository at this point in the history
Refactor: ์ „๋ฐ˜์ ์ธ ๋ฏธ๋””์–ด ์„œ๋ฒ„ ๊ตฌ์กฐ ๊ฐœ์„ 
  • Loading branch information
platinouss authored Mar 2, 2024
2 parents 4708e7a + f98bb02 commit 9af87ba
Show file tree
Hide file tree
Showing 22 changed files with 501 additions and 377 deletions.
1 change: 1 addition & 0 deletions mediaServer/.gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
/dist
/node_modules
/output
/heapdump

# Logs
logs
Expand Down
310 changes: 1 addition & 309 deletions mediaServer/src/RelayServer.ts
Original file line number Diff line number Diff line change
@@ -1,29 +1,6 @@
import { Server, Socket } from 'socket.io';
import { RTCIceCandidate, RTCPeerConnection } from 'wrtc';
import { pc_config } from './config/pc.config';
import { RoomConnectionInfo } from './models/RoomConnectionInfo';
import { ClientConnectionInfo } from './models/ClientConnectionInfo';
import { ClientType } from './constants/client-type.constant';
import { Message } from './models/Message';
import { mediaConverter } from './utils/MediaConverter';
import { getEmailByJwtPayload } from './utils/auth';
import { findClientInfoByEmail, saveClientInfo, sendDataToReconnectPresenter } from './services/client.service';
import { deleteRoomInfoById, findRoomInfoById, saveRoomInfo, updateWhiteboardData } from './services/room.service';
import { RoomInfoDto } from './dto/room-info.dto';
import {
deleteQuestionStream,
findQuestion,
getStreamKeyAndQuestionFromStream,
isQuestionStreamExisted,
saveQuestion,
setQuestionStreamAndGroup,
updateQuestionStatus
} from './services/question-service';
import { StreamReadRaw } from './types/redis-stream.type';
import { isCreatedRoomAndNotEqualPresenterEmail } from './validation/request.validation';
import { AskedRequestDto } from './dto/asked.request.dto';
import { ServerAnswerDto } from './dto/serverAnswer.dto';
import { getClientEmail, setParticipantConnection, setPresenterMediaStream } from './services/participant.service';

export class RelayServer {
private readonly _io;
Expand All @@ -50,296 +27,11 @@ export class RelayServer {
return this._roomsConnectionInfo;
}

get clientConnectionInfo() {
get clientsConnectionInfo() {
return this._clientsConnectionInfo;
}

listen = (path: string, event: string, method: (socket: Socket) => void) => {
this._io.of(path).on(event, method);
};

createRoom = (socket: Socket) => {
try {
const email: string = getEmailByJwtPayload(socket.handshake.auth.accessToken);
if (this._clientsConnectionInfo.has(email)) {
// TODO: ์ด๋ฏธ ์ฐธ์—ฌ ์ค‘์ธ ๋ฐฉ์ด ์žˆ์„ ๋•Œ, ํด๋ผ์ด์–ธํŠธํ•œํ…Œ ์žฌ ์ฐธ์—ฌํ• ๊ฑด์ง€ ๋ฌผ์–ด๋ณด๊ธฐ. ์•„๋‹ˆ๋ฉด ํ•œ ๊ฐ•์˜์‹ค๋งŒ ์ฐธ์—ฌํ•  ์ˆ˜ ์žˆ๋‹ค๊ณ  ํ•ด๋„ ์ข‹์Œ
}

socket.on('presenterOffer', async (data) => {
const roomInfo = await findRoomInfoById(data.roomId);
if (isCreatedRoomAndNotEqualPresenterEmail(email, roomInfo)) {
console.log('์ด๋ฏธ ์กด์žฌํ•˜๋Š” ๊ฐ•์˜์‹ค์ž…๋‹ˆ๋‹ค.');
return;
}
const RTCPC = new RTCPeerConnection(pc_config);
this._clientsConnectionInfo.set(email, new ClientConnectionInfo(RTCPC));
socket.join(email);
if (roomInfo.presenterEmail !== email) {
this._roomsConnectionInfo.set(data.roomId, new RoomConnectionInfo(RTCPC));
if (await isQuestionStreamExisted(data.roomId)) {
await deleteQuestionStream(data.roomId);
}
await setQuestionStreamAndGroup(data.roomId);
await Promise.all([
saveClientInfo(email, ClientType.PRESENTER, data.roomId),
saveRoomInfo(data.roomId, new RoomInfoDto(email, data.whiteboard))
]);
}
if (roomInfo.presenterEmail === email) {
await sendDataToReconnectPresenter(email, data.roomId, roomInfo);
}

RTCPC.ontrack = (event) => {
const roomInfo = this._roomsConnectionInfo.get(data.roomId);
if (roomInfo) {
roomInfo.stream = event.streams[0];
roomInfo.studentInfoList.forEach((clientConnectionInfo: ClientConnectionInfo) => {
event.streams[0].getTracks().forEach((track: any) => {
clientConnectionInfo.RTCPC.getSenders()[0].replaceTrack(track);
});
});
mediaConverter.setSink(event.streams[0], data.roomId);
}
};
this.exchangeCandidate('/create-room', email, socket);

await RTCPC.setRemoteDescription(data.SDP);
const SDP = await RTCPC.createAnswer();
this._io.of('/create-room').to(email).emit('serverAnswer', {
SDP: SDP
});
RTCPC.setLocalDescription(SDP);

const clientConnectionInfo = this._clientsConnectionInfo.get(email);
if (!clientConnectionInfo) {
throw new Error('ํ•ด๋‹น ๋ฐœํ‘œ์ž๊ฐ€ ์กด์žฌํ•˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค.');
}
clientConnectionInfo.enterSocket = socket;
});
} catch (e) {
console.log(e);
}
};

enterRoom = (socket: Socket) => {
try {
const email = getClientEmail(socket);
const clientType = email ? ClientType.STUDENT : ClientType.GUEST;
const clientId = email ? email : socket.id;
socket.on('studentOffer', async (data) => {
const RTCPC = setParticipantConnection(clientId);
await saveClientInfo(clientId, clientType, data.roomId);
setPresenterMediaStream(RTCPC, data.roomId);
socket.join(clientId);
this.exchangeCandidate('/enter-room', clientId, socket);
const [result, roomInfo, SDP] = await Promise.all([
RTCPC.setRemoteDescription(data.SDP),
findRoomInfoById(data.roomId),
RTCPC.createAnswer()
]);
if (!roomInfo.currentWhiteboardData) {
console.log('์ •์ƒ์ ์ธ ์ ‘๊ทผ์ด ์•„๋‹™๋‹ˆ๋‹ค');
return;
}
const answerData = new ServerAnswerDto(JSON.parse(roomInfo.currentWhiteboardData), roomInfo.startTime, SDP);
this._io.of('/enter-room').to(clientId).emit(`serverAnswer`, answerData);
RTCPC.setLocalDescription(SDP);
const clientConnectionInfo = this._clientsConnectionInfo.get(clientId) as ClientConnectionInfo;
clientConnectionInfo.enterSocket = socket;
});
} catch (e) {
console.log(e);
}
};

// TODO: ํด๋ผ์ด์–ธํŠธ๋Š” ํ•œ ๊ฐœ์˜ ๋ฐฉ๋งŒ ์ ‘์†ํ•  ์ˆ˜ ์žˆ๋Š”์ง€? ๋งŒ์•ฝ ๊ทธ๋ ‡๋‹ค๋ฉด, ์ด๋ฏธ ์ฐธ์—ฌ ์ค‘์ธ ๋น™์ด ์žˆ์„ ๋•Œ ์š”์ฒญ ๊ฑฐ๋ถ€ํ•˜๋„๋ก ์ฒ˜๋ฆฌํ•ด์•ผ ํ•จ
lecture = async (socket: Socket) => {
const email = getClientEmail(socket);
if (!email) {
await this.guestLecture(socket);
return;
}
const clientInfo = await findClientInfoByEmail(email);
const clientConnectionInfo = await this._clientsConnectionInfo.get(email);
if (!clientInfo || !clientConnectionInfo || !clientInfo.roomId) {
// TODO: ์ถ”ํ›„ ํด๋ผ์ด์–ธํŠธ๋กœ ์—๋Ÿฌ์ฒ˜๋ฆฌ ํ•„์š”
console.log('์ž˜๋ชป๋œ ์š”์ฒญ์ž…๋‹ˆ๋‹ค.');
return;
}
const roomInfo = await findRoomInfoById(clientInfo.roomId);
const roomConnectionInfo = this._roomsConnectionInfo.get(clientInfo.roomId);
if (!roomConnectionInfo) {
// TODO: ์ถ”ํ›„ ํด๋ผ์ด์–ธํŠธ๋กœ ์—๋Ÿฌ์ฒ˜๋ฆฌ ํ•„์š”
console.log('์•„์ง ์—ด๋ฆฌ์ง€ ์•Š์•˜๊ฑฐ๋‚˜ ์ข…๋ฃŒ๋œ ๋ฐฉ์ž…๋‹ˆ๋‹ค.');
return;
}
clientConnectionInfo.lectureSocket = socket;
socket.join(clientInfo.roomId);
if (clientInfo.type === ClientType.PRESENTER) {
roomConnectionInfo.presenterSocket = socket;
socket.join(email);
}
if (clientInfo.type === ClientType.STUDENT) {
roomConnectionInfo.studentInfoList.add(clientConnectionInfo);
// TODO: API ์„œ๋ฒ„์— ๊ฐ•์˜ ์‹œ์ž‘ ์š”์ฒญํ•˜๊ธฐ
fetch((process.env.SERVER_API_URL + '/lecture/' + clientInfo.roomId) as string, {
method: 'PATCH',
headers: { Authorization: socket.handshake.auth.accessToken }
}).then((response) => console.log('๊ฐ•์˜ ์‹œ์ž‘:' + response.status));
}

socket.on('edit', async (data) => {
if (clientInfo.type !== ClientType.PRESENTER || clientInfo.roomId !== data.roomId) {
// TODO: ์ถ”ํ›„ ํด๋ผ์ด์–ธํŠธ๋กœ ์—๋Ÿฌ์ฒ˜๋ฆฌ ํ•„์š”
console.log('ํ•ด๋‹น ๋ฐœํ‘œ์ž๊ฐ€ ์กด์žฌํ•˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค.');
return;
}
// TODO: API ์„œ๋ฒ„๋กœ ํ™”์ดํŠธ๋ณด๋“œ ๋ฐ์ดํ„ฐ ์ „๋‹ฌ
fetch(process.env.SERVER_API_URL + '/lecture/log/' + data.roomId, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(data.content)
});
await Promise.all([
updateWhiteboardData(data.roomId, data.content),
this._io.of('/lecture').to(clientInfo.roomId).emit('update', new Message(data.type, data.content))
]);
});

socket.on('ask', async (data) => {
if (clientInfo.type !== ClientType.STUDENT || clientInfo.roomId !== data.roomId) {
// TODO: ์ถ”ํ›„ ํด๋ผ์ด์–ธํŠธ๋กœ ์—๋Ÿฌ์ฒ˜๋ฆฌ ํ•„์š”
console.log('ํ•ด๋‹น ์ฐธ์—ฌ์ž๊ฐ€ ์กด์žฌํ•˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค.');
return;
}
const presenterEmail = roomInfo.presenterEmail;
if (!presenterEmail) {
// TODO: ์ถ”ํ›„ ํด๋ผ์ด์–ธํŠธ๋กœ ์—๋Ÿฌ์ฒ˜๋ฆฌ ํ•„์š”
console.log('๋ฐœํ‘œ์ž๊ฐ€ ์—†์Šต๋‹ˆ๋‹ค.');
return;
}
await saveQuestion(data.roomId, data.content);
const streamData = (await findQuestion(data.roomId, presenterEmail)) as StreamReadRaw;
const question = getStreamKeyAndQuestionFromStream(streamData);
this._io
.of('/lecture')
.to(presenterEmail)
.emit('asked', new AskedRequestDto(data.type, question.content, question.streamKey));
});

socket.on('solved', async (data) => {
if (clientInfo.type !== ClientType.PRESENTER || clientInfo.roomId !== data.roomId) {
console.log('ํ•ด๋‹น ๊ฐ•์˜์‹ค ๋ฐœํ‘œ์ž๋งŒ ์งˆ๋ฌธ์„ ์™„๋ฃŒํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.');
return;
}
await updateQuestionStatus(data.roomId, data.questionId);
});

socket.on('response', (data) => {
if (data.type === 'question') {
updateQuestionStatus(data.roomId, data.questionId);
}
});

socket.on('end', async (data) => {
if (clientInfo.type !== ClientType.PRESENTER || clientInfo.roomId !== data.roomId) {
// TODO: ์ถ”ํ›„ ํด๋ผ์ด์–ธํŠธ๋กœ ์—๋Ÿฌ์ฒ˜๋ฆฌ ํ•„์š”
console.log('ํ•ด๋‹น ๋ฐœํ‘œ์ž๊ฐ€ ์กด์žฌํ•˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค');
return;
}
this._io.of('/lecture').to(data.roomId).emit('ended', new Message(data.type, 'finish'));
mediaConverter.setFfmpeg(data.roomId);
this._roomsConnectionInfo.get(clientInfo.roomId)?.endLecture(data.roomId);
this._roomsConnectionInfo.delete(clientInfo.roomId);
this._clientsConnectionInfo.delete(email);
await Promise.all([deleteRoomInfoById(data.roomId), deleteQuestionStream(data.roomId)]);
});

socket.on('leave', (data) => {
if (clientInfo.type !== ClientType.STUDENT || clientInfo.roomId !== data.roomId) {
// TODO: ์ถ”ํ›„ ํด๋ผ์ด์–ธํŠธ๋กœ ์—๋Ÿฌ์ฒ˜๋ฆฌ ํ•„์š”
console.log('ํ•ด๋‹น ์ฐธ์—ฌ์ž๊ฐ€ ์กด์žฌํ•˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค');
return;
}
this._roomsConnectionInfo.get(clientInfo.roomId)?.exitRoom(clientConnectionInfo, data.roomId);
this._io.of('/lecture').to(clientInfo.roomId).emit('response', new Message(data.type, 'success'));
});
};

guestLecture = async (socket: Socket) => {
const clientId = socket.handshake.auth.accessToken;
if (!clientId) {
console.log('์ž˜๋ชป ๋œ ์ ‘๊ทผ์ž…๋‹ˆ๋‹ค.');
return;
}
const clientInfo = await findClientInfoByEmail(clientId);
const roomInfo = await findRoomInfoById(clientInfo.roomId);
const clientConnectionInfo = await this._clientsConnectionInfo.get(clientId);
const roomConnectionInfo = this._roomsConnectionInfo.get(clientInfo.roomId);
if (!clientInfo || !clientConnectionInfo || !clientInfo.roomId) {
// TODO: ์ถ”ํ›„ ํด๋ผ์ด์–ธํŠธ๋กœ ์—๋Ÿฌ์ฒ˜๋ฆฌ ํ•„์š”
console.log('์ž˜๋ชป๋œ ์š”์ฒญ์ž…๋‹ˆ๋‹ค.');
return;
}
if (!roomConnectionInfo) {
// TODO: ์ถ”ํ›„ ํด๋ผ์ด์–ธํŠธ๋กœ ์—๋Ÿฌ์ฒ˜๋ฆฌ ํ•„์š”
console.log('์•„์ง ์—ด๋ฆฌ์ง€ ์•Š์•˜๊ฑฐ๋‚˜ ์ข…๋ฃŒ๋œ ๋ฐฉ์ž…๋‹ˆ๋‹ค.');
return;
}
socket.join(clientInfo.roomId);
roomConnectionInfo.studentInfoList.add(clientConnectionInfo);

socket.on('ask', async (data) => {
if (clientInfo.type !== ClientType.GUEST || clientInfo.roomId !== data.roomId) {
// TODO: ์ถ”ํ›„ ํด๋ผ์ด์–ธํŠธ๋กœ ์—๋Ÿฌ์ฒ˜๋ฆฌ ํ•„์š”
console.log('ํ•ด๋‹น ์ฐธ์—ฌ์ž๊ฐ€ ์กด์žฌํ•˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค.');
return;
}
const presenterEmail = roomInfo.presenterEmail;
if (!presenterEmail) {
// TODO: ์ถ”ํ›„ ํด๋ผ์ด์–ธํŠธ๋กœ ์—๋Ÿฌ์ฒ˜๋ฆฌ ํ•„์š”
console.log('๋ฐœํ‘œ์ž๊ฐ€ ์—†์Šต๋‹ˆ๋‹ค.');
return;
}
await saveQuestion(data.roomId, data.content);
const streamData = (await findQuestion(data.roomId, presenterEmail)) as StreamReadRaw;
const question = getStreamKeyAndQuestionFromStream(streamData);
this._io
.of('/lecture')
.to(presenterEmail)
.emit('asked', new AskedRequestDto(data.type, question.content, question.streamKey));
});

socket.on('leave', (data) => {
if (clientInfo.type !== ClientType.GUEST || clientInfo.roomId !== data.roomId) {
// TODO: ์ถ”ํ›„ ํด๋ผ์ด์–ธํŠธ๋กœ ์—๋Ÿฌ์ฒ˜๋ฆฌ ํ•„์š”
console.log('ํ•ด๋‹น ์ฐธ์—ฌ์ž๊ฐ€ ์กด์žฌํ•˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค');
return;
}
this._roomsConnectionInfo.get(clientInfo.roomId)?.exitRoom(clientConnectionInfo, data.roomId);
this._io.of('/lecture').to(clientInfo.roomId).emit('response', new Message(data.type, 'success'));
});
};

exchangeCandidate = (namespace: string, email: string, socket: Socket) => {
try {
const RTCPC = this._clientsConnectionInfo.get(email)?.RTCPC;
if (!RTCPC) {
console.log('candidate๋ฅผ ๊ตํ™˜ํ•  ์ˆ˜ ์—†์Šต๋‹ˆ๋‹ค.');
return;
}
RTCPC.onicecandidate = (e) => {
if (e.candidate) {
this._io.of(namespace).to(email).emit(`serverCandidate`, {
candidate: e.candidate
});
}
};
socket.on('clientCandidate', (data) => {
RTCPC.addIceCandidate(new RTCIceCandidate(data.candidate));
});
} catch (e) {
console.log(e);
}
};
}
5 changes: 5 additions & 0 deletions mediaServer/src/constants/message-type.constant.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
export enum MessageType {
LECTURE = 'lecture',
WHITEBOARD = 'whiteBoard',
QUESTION = 'question'
}
File renamed without changes.
Loading

0 comments on commit 9af87ba

Please sign in to comment.