From dc011115b069cb5c294e794094cae9c33401c158 Mon Sep 17 00:00:00 2001 From: JongBin Noh <95959567+begong313@users.noreply.github.com> Date: Tue, 3 Dec 2024 23:03:35 +0900 Subject: [PATCH 01/33] =?UTF-8?q?feat:=20=EB=B0=A9=20=EA=B4=80=EB=A6=AC?= =?UTF-8?q?=EC=9E=90=20socketid=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/media/src/mediasoup/mediasoup.service.ts | 4 ++-- apps/media/src/room/room.service.ts | 4 ++-- apps/media/src/room/room.ts | 4 +++- apps/media/src/signaling/signaling.gateway.ts | 4 ++-- 4 files changed, 9 insertions(+), 7 deletions(-) diff --git a/apps/media/src/mediasoup/mediasoup.service.ts b/apps/media/src/mediasoup/mediasoup.service.ts index 6d2467f6..748b833e 100644 --- a/apps/media/src/mediasoup/mediasoup.service.ts +++ b/apps/media/src/mediasoup/mediasoup.service.ts @@ -48,7 +48,7 @@ export class MediasoupService implements OnModuleInit { return worker; } - async createRoom(roomId: string) { + async createRoom(roomId: string, masterSocketId: string) { const isExistRoom = this.roomService.existRoom(roomId); if (isExistRoom) { return roomId; @@ -59,7 +59,7 @@ export class MediasoupService implements OnModuleInit { mediaCodecs: this.mediasoupConfig.router.mediaCodecs, }); - return this.roomService.createRoom(roomId, router); + return this.roomService.createRoom(roomId, router, masterSocketId); } joinRoom(roomId: string, socketId: string, nickname: string) { diff --git a/apps/media/src/room/room.service.ts b/apps/media/src/room/room.service.ts index 848fc443..53cd5872 100644 --- a/apps/media/src/room/room.service.ts +++ b/apps/media/src/room/room.service.ts @@ -11,8 +11,8 @@ export class RoomService { constructor() {} - createRoom(roomId: string, router: Router) { - const room = new Room(roomId, router); + createRoom(roomId: string, router: Router, masterSocketId: string) { + const room = new Room(roomId, router, masterSocketId); this.rooms.set(roomId, room); return roomId; } diff --git a/apps/media/src/room/room.ts b/apps/media/src/room/room.ts index e2485668..e7f5fb41 100644 --- a/apps/media/src/room/room.ts +++ b/apps/media/src/room/room.ts @@ -6,12 +6,14 @@ import { Peer } from './peer'; export class Room { id: string; + masterSocketId: string; router: Router; peers: Map; - constructor(roomId: string, router: Router) { + constructor(roomId: string, router: Router, masterSocketId: string) { this.id = roomId; this.router = router; + this.masterSocketId = masterSocketId; this.peers = new Map(); } diff --git a/apps/media/src/signaling/signaling.gateway.ts b/apps/media/src/signaling/signaling.gateway.ts index 162d8fe5..c46be289 100644 --- a/apps/media/src/signaling/signaling.gateway.ts +++ b/apps/media/src/signaling/signaling.gateway.ts @@ -24,8 +24,8 @@ export class SignalingGateway implements OnGatewayDisconnect { ) {} @SubscribeMessage(SOCKET_EVENTS.createRoom) - async handleCreateRoom(@MessageBody('roomId') roomId: string) { - await this.mediasoupService.createRoom(roomId); + async handleCreateRoom(@ConnectedSocket() client: Socket, @MessageBody('roomId') roomId: string) { + await this.mediasoupService.createRoom(roomId, client.id); return { roomId }; } From ab0199a9a989547db640ea638c5577325641aeb2 Mon Sep 17 00:00:00 2001 From: JongBin Noh <95959567+begong313@users.noreply.github.com> Date: Tue, 3 Dec 2024 23:03:35 +0900 Subject: [PATCH 02/33] =?UTF-8?q?feat:=20=EB=B0=A9=20=EA=B4=80=EB=A6=AC?= =?UTF-8?q?=EC=9E=90=20socketid=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/media/src/mediasoup/mediasoup.service.ts | 4 ++-- apps/media/src/room/room.service.ts | 4 ++-- apps/media/src/room/room.ts | 4 +++- apps/media/src/signaling/signaling.gateway.ts | 4 ++-- 4 files changed, 9 insertions(+), 7 deletions(-) diff --git a/apps/media/src/mediasoup/mediasoup.service.ts b/apps/media/src/mediasoup/mediasoup.service.ts index d389c86b..5b4549dd 100644 --- a/apps/media/src/mediasoup/mediasoup.service.ts +++ b/apps/media/src/mediasoup/mediasoup.service.ts @@ -48,7 +48,7 @@ export class MediasoupService implements OnModuleInit { return worker; } - async createRoom(roomId: string) { + async createRoom(roomId: string, masterSocketId: string) { const isExistRoom = this.roomService.existRoom(roomId); if (isExistRoom) { return roomId; @@ -59,7 +59,7 @@ export class MediasoupService implements OnModuleInit { mediaCodecs: this.mediasoupConfig.router.mediaCodecs, }); - return this.roomService.createRoom(roomId, router); + return this.roomService.createRoom(roomId, router, masterSocketId); } joinRoom(roomId: string, socketId: string, nickname: string) { diff --git a/apps/media/src/room/room.service.ts b/apps/media/src/room/room.service.ts index 848fc443..53cd5872 100644 --- a/apps/media/src/room/room.service.ts +++ b/apps/media/src/room/room.service.ts @@ -11,8 +11,8 @@ export class RoomService { constructor() {} - createRoom(roomId: string, router: Router) { - const room = new Room(roomId, router); + createRoom(roomId: string, router: Router, masterSocketId: string) { + const room = new Room(roomId, router, masterSocketId); this.rooms.set(roomId, room); return roomId; } diff --git a/apps/media/src/room/room.ts b/apps/media/src/room/room.ts index e2485668..e7f5fb41 100644 --- a/apps/media/src/room/room.ts +++ b/apps/media/src/room/room.ts @@ -6,12 +6,14 @@ import { Peer } from './peer'; export class Room { id: string; + masterSocketId: string; router: Router; peers: Map; - constructor(roomId: string, router: Router) { + constructor(roomId: string, router: Router, masterSocketId: string) { this.id = roomId; this.router = router; + this.masterSocketId = masterSocketId; this.peers = new Map(); } diff --git a/apps/media/src/signaling/signaling.gateway.ts b/apps/media/src/signaling/signaling.gateway.ts index 047bbd49..5af73c88 100644 --- a/apps/media/src/signaling/signaling.gateway.ts +++ b/apps/media/src/signaling/signaling.gateway.ts @@ -24,8 +24,8 @@ export class SignalingGateway implements OnGatewayDisconnect { ) {} @SubscribeMessage(SOCKET_EVENTS.createRoom) - async handleCreateRoom(@MessageBody('roomId') roomId: string) { - await this.mediasoupService.createRoom(roomId); + async handleCreateRoom(@ConnectedSocket() client: Socket, @MessageBody('roomId') roomId: string) { + await this.mediasoupService.createRoom(roomId, client.id); return { roomId }; } From 68f940a082852ee09de11b8879306bca407626c9 Mon Sep 17 00:00:00 2001 From: JongBin Noh <95959567+begong313@users.noreply.github.com> Date: Wed, 4 Dec 2024 02:03:18 +0900 Subject: [PATCH 03/33] =?UTF-8?q?feat:=20=ED=95=A8=EC=88=98=EB=AA=85=20?= =?UTF-8?q?=EB=B3=80=EA=B2=BD,=20=EB=A9=94=EB=AA=A8=EB=A6=AC=EB=88=84?= =?UTF-8?q?=EC=88=98=EC=9A=94=EC=86=8C=20=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/media/src/record/record.service.ts | 2 +- apps/media/src/record/recordInfo.ts | 5 +++-- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/apps/media/src/record/record.service.ts b/apps/media/src/record/record.service.ts index 6873d333..666ae346 100644 --- a/apps/media/src/record/record.service.ts +++ b/apps/media/src/record/record.service.ts @@ -117,7 +117,7 @@ export class RecordService { return; } this.releasePort(recordInfo.port); - recordInfo.stopRecordProcess(); + recordInfo.clearStream(); this.recordInfos.delete(roomId); } diff --git a/apps/media/src/record/recordInfo.ts b/apps/media/src/record/recordInfo.ts index 62d2168c..2d829649 100644 --- a/apps/media/src/record/recordInfo.ts +++ b/apps/media/src/record/recordInfo.ts @@ -43,7 +43,7 @@ export class RecordInfo { this.recordConsumer.resume(); } - stopRecordProcess() { + clearStream() { if (this.recordConsumer) { this.recordConsumer.close(); this.recordConsumer = null; @@ -81,9 +81,10 @@ export class RecordInfo { console.log('FFmpeg error:1', err); }) .on('end', () => { - this.ncpService.uploadFile(filePath, remoteFileName, roomId); + // this.ncpService.uploadFile(filePath, remoteFileName, roomId); unlinkSync(sdpFilePath); this.ffmpegProcess = null; + this.clearStream(); }) .save(filePath); From 34f71330e18415e5d6d3911662c2a5e8c1cd8f72 Mon Sep 17 00:00:00 2001 From: JongBin Noh <95959567+begong313@users.noreply.github.com> Date: Wed, 4 Dec 2024 12:36:43 +0900 Subject: [PATCH 04/33] =?UTF-8?q?feat:=20=EB=B0=A9=20=EB=8B=AB=EA=B8=B0=20?= =?UTF-8?q?=EB=A1=9C=EC=A7=81=20=EC=A0=95=EB=A6=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit disconnect event 시 방장이 나가면 방 삭제, 만약 녹화중이라면 녹화종료 방이 이미 삭제된 경우 peer-left 이벤트를 발송하지 않게 하여 불필요한 에러 줄임. --- apps/media/src/record/record.service.ts | 4 ---- apps/media/src/room/room.service.ts | 17 +++++++++-------- apps/media/src/signaling/signaling.gateway.ts | 17 ++++++++++++----- apps/media/src/signaling/signaling.module.ts | 3 ++- 4 files changed, 23 insertions(+), 18 deletions(-) diff --git a/apps/media/src/record/record.service.ts b/apps/media/src/record/record.service.ts index 666ae346..e29a1b26 100644 --- a/apps/media/src/record/record.service.ts +++ b/apps/media/src/record/record.service.ts @@ -66,10 +66,6 @@ export class RecordService { return recordInfo; } - getRecordInfo(roomId: string) { - return this.recordInfos.get(roomId); - } - private async addPlainTransport(recordInfo: RecordInfo, router: types.Router) { const plainTransport = await this.mediasoupService.createPlainTransport(router); recordInfo.setPlainTransport(plainTransport); diff --git a/apps/media/src/room/room.service.ts b/apps/media/src/room/room.service.ts index 53cd5872..8a4fe87d 100644 --- a/apps/media/src/room/room.service.ts +++ b/apps/media/src/room/room.service.ts @@ -31,14 +31,7 @@ export class RoomService { deletePeer(socketId: string) { for (const [roomId, room] of this.rooms) { - if (!room.removePeer(socketId)) continue; - - if (room.peers.size === 0) { - room.close(); - this.rooms.delete(roomId); - } - - return roomId; + if (room.removePeer(socketId)) return roomId; } } @@ -48,4 +41,12 @@ export class RoomService { this.rooms.delete(roomId); return roomId; } + + checkIsMaster(roomId: string, socketId: string) { + const room = this.rooms.get(roomId); + if (!room) { + return false; + } + return room.masterSocketId === socketId; + } } diff --git a/apps/media/src/signaling/signaling.gateway.ts b/apps/media/src/signaling/signaling.gateway.ts index 5af73c88..8457c989 100644 --- a/apps/media/src/signaling/signaling.gateway.ts +++ b/apps/media/src/signaling/signaling.gateway.ts @@ -13,6 +13,7 @@ import type { client, server } from '@repo/mediasoup'; import { MediasoupService } from '@/mediasoup/mediasoup.service'; import { RecordService } from '@/record/record.service'; +import { RoomService } from '@/room/room.service'; import { WSExceptionFilter } from '@/wsException.filter'; @WebSocketGateway() @@ -20,7 +21,8 @@ import { WSExceptionFilter } from '@/wsException.filter'; export class SignalingGateway implements OnGatewayDisconnect { constructor( private mediasoupService: MediasoupService, - private recordService: RecordService + private recordService: RecordService, + private roomService: RoomService ) {} @SubscribeMessage(SOCKET_EVENTS.createRoom) @@ -104,12 +106,18 @@ export class SignalingGateway implements OnGatewayDisconnect { handleDisconnect(@ConnectedSocket() client: Socket) { const roomId = this.mediasoupService.disconnect(client.id); - const recordInfo = this.recordService.getRecordInfo(roomId); - if (recordInfo && recordInfo.socketId === client.id) { + const isMaster = this.roomService.checkIsMaster(roomId, client.id); + if (isMaster) { + client.to(roomId).emit(SOCKET_EVENTS.roomClosed); this.recordService.stopRecord(roomId); + this.mediasoupService.closeRoom(roomId); + return; } - client.to(roomId).emit(SOCKET_EVENTS.peerLeft, { peerId: client.id }); + const isExistRoom = this.roomService.existRoom(roomId); + if (isExistRoom) { + client.to(roomId).emit(SOCKET_EVENTS.peerLeft, { peerId: client.id }); + } } @SubscribeMessage(SOCKET_EVENTS.closeProducer) @@ -188,7 +196,6 @@ export class SignalingGateway implements OnGatewayDisconnect { @SubscribeMessage(SOCKET_EVENTS.closeRoom) closeMeetingRoom(@ConnectedSocket() client: Socket, @MessageBody('roomId') roomId: string) { client.to(roomId).emit(SOCKET_EVENTS.roomClosed); - this.mediasoupService.closeRoom(roomId); } @SubscribeMessage(SOCKET_EVENTS.startRecord) diff --git a/apps/media/src/signaling/signaling.module.ts b/apps/media/src/signaling/signaling.module.ts index a3bf7294..82c90837 100644 --- a/apps/media/src/signaling/signaling.module.ts +++ b/apps/media/src/signaling/signaling.module.ts @@ -2,11 +2,12 @@ import { Module } from '@nestjs/common'; import { MediasoupModule } from '@/mediasoup/mediasoup.module'; import { RecordModule } from '@/record/record.module'; +import { RoomModule } from '@/room/room.module'; import { SignalingGateway } from './signaling.gateway'; @Module({ - imports: [MediasoupModule, RecordModule], + imports: [MediasoupModule, RecordModule, RoomModule], providers: [SignalingGateway], exports: [SignalingGateway], }) From dd97ebbcb141686253f8aa83d8e705c33369717b Mon Sep 17 00:00:00 2001 From: JongBin Noh <95959567+begong313@users.noreply.github.com> Date: Thu, 5 Dec 2024 02:15:02 +0900 Subject: [PATCH 05/33] =?UTF-8?q?feat:=20=EC=9D=B4=EB=B2=A4=ED=8A=B8=20?= =?UTF-8?q?=ED=9A=9F=EC=88=98=20=EC=B5=9C=EC=A0=81=ED=99=94?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/media/src/room/room.service.ts | 16 ++++++++++++++++ apps/media/src/room/room.ts | 2 ++ apps/media/src/signaling/signaling.gateway.ts | 7 ++++--- 3 files changed, 22 insertions(+), 3 deletions(-) diff --git a/apps/media/src/room/room.service.ts b/apps/media/src/room/room.service.ts index 8a4fe87d..cecda26f 100644 --- a/apps/media/src/room/room.service.ts +++ b/apps/media/src/room/room.service.ts @@ -49,4 +49,20 @@ export class RoomService { } return room.masterSocketId === socketId; } + + checkRoomIsOpen(roomId: string) { + const room = this.rooms.get(roomId); + if (!room) { + return false; + } + return room.isOpen; + } + + setRoomIsOpen(roomId: string, isOpen: boolean) { + const room = this.rooms.get(roomId); + if (!room) { + return; + } + room.isOpen = isOpen; + } } diff --git a/apps/media/src/room/room.ts b/apps/media/src/room/room.ts index e7f5fb41..224941cc 100644 --- a/apps/media/src/room/room.ts +++ b/apps/media/src/room/room.ts @@ -9,12 +9,14 @@ export class Room { masterSocketId: string; router: Router; peers: Map; + isOpen: boolean; constructor(roomId: string, router: Router, masterSocketId: string) { this.id = roomId; this.router = router; this.masterSocketId = masterSocketId; this.peers = new Map(); + this.isOpen = true; } getRouter() { diff --git a/apps/media/src/signaling/signaling.gateway.ts b/apps/media/src/signaling/signaling.gateway.ts index 8457c989..89328120 100644 --- a/apps/media/src/signaling/signaling.gateway.ts +++ b/apps/media/src/signaling/signaling.gateway.ts @@ -34,8 +34,8 @@ export class SignalingGateway implements OnGatewayDisconnect { @SubscribeMessage(SOCKET_EVENTS.joinRoom) joinRoom(@ConnectedSocket() client: Socket, @MessageBody() joinRoomDto: server.JoinRoomDto) { const { roomId, nickname } = joinRoomDto; - client.join(roomId); const rtpCapabilities = this.mediasoupService.joinRoom(roomId, client.id, nickname); + client.join(roomId); client.to(roomId).emit(SOCKET_EVENTS.newPeer, { peerId: client.id, nickname }); return { rtpCapabilities }; } @@ -114,8 +114,8 @@ export class SignalingGateway implements OnGatewayDisconnect { return; } - const isExistRoom = this.roomService.existRoom(roomId); - if (isExistRoom) { + const isOpen = this.roomService.checkRoomIsOpen(roomId); + if (isOpen) { client.to(roomId).emit(SOCKET_EVENTS.peerLeft, { peerId: client.id }); } } @@ -196,6 +196,7 @@ export class SignalingGateway implements OnGatewayDisconnect { @SubscribeMessage(SOCKET_EVENTS.closeRoom) closeMeetingRoom(@ConnectedSocket() client: Socket, @MessageBody('roomId') roomId: string) { client.to(roomId).emit(SOCKET_EVENTS.roomClosed); + this.roomService.setRoomIsOpen(roomId, false); } @SubscribeMessage(SOCKET_EVENTS.startRecord) From c72050bf4a48b8567584f6dc2a2b9a04a53fc62b Mon Sep 17 00:00:00 2001 From: seoko97 Date: Thu, 5 Dec 2024 12:38:35 +0900 Subject: [PATCH 06/33] =?UTF-8?q?fix:=20MediasoupConfig=EC=97=90=20H264=20?= =?UTF-8?q?=EB=B9=84=EB=94=94=EC=98=A4=20=EC=BD=94=EB=8D=B1=20=EC=B6=94?= =?UTF-8?q?=EA=B0=80=20=EB=B0=8F=20VP8=20=EC=BD=94=EB=8D=B1=EC=9D=98=20?= =?UTF-8?q?=ED=8C=8C=EB=9D=BC=EB=AF=B8=ED=84=B0=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/media/src/mediasoup/config.ts | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/apps/media/src/mediasoup/config.ts b/apps/media/src/mediasoup/config.ts index 7371a5e8..0d27a8f5 100644 --- a/apps/media/src/mediasoup/config.ts +++ b/apps/media/src/mediasoup/config.ts @@ -29,10 +29,22 @@ export class MediasoupConfig { useinbandfec: 1, }, }, + { + kind: 'video', + mimeType: 'video/H264', + clockRate: 90000, + parameters: { + 'packetization-mode': 1, + 'profile-level-id': '42e01f', + }, + }, { kind: 'video', mimeType: 'video/VP8', clockRate: 90000, + parameters: { + 'x-google-start-bitrate': 10000, + }, }, ] as RtpCodecCapability[], }; From 461a53f49176728ad8989f13577813b3e5332201 Mon Sep 17 00:00:00 2001 From: seoko97 Date: Thu, 5 Dec 2024 12:38:53 +0900 Subject: [PATCH 07/33] =?UTF-8?q?fix:=20=ED=81=B4=EB=9D=BC=EC=9D=B4?= =?UTF-8?q?=EC=96=B8=ED=8A=B8=20=EC=B8=A1=20=EC=BD=94=EB=8D=B1=20=EC=A0=95?= =?UTF-8?q?=EB=B3=B4=EC=99=80=20=EC=84=9C=EB=B2=84=EC=B8=A1=20=EC=BD=94?= =?UTF-8?q?=EB=8D=B1=20=EC=A0=95=EB=B3=B4=EA=B0=80=20=EC=9D=BC=EC=B9=98?= =?UTF-8?q?=ED=95=98=EC=A7=80=20=EC=95=8A=EC=95=84=20=EB=B0=9C=EC=83=9D?= =?UTF-8?q?=ED=95=98=EB=8A=94=20=EB=B2=84=EA=B7=B8=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/media/src/mediasoup/mediasoup.service.ts | 16 ++++++++++------ packages/mediasoup/src/server/index.ts | 11 ----------- 2 files changed, 10 insertions(+), 17 deletions(-) diff --git a/apps/media/src/mediasoup/mediasoup.service.ts b/apps/media/src/mediasoup/mediasoup.service.ts index d389c86b..9cfcab4f 100644 --- a/apps/media/src/mediasoup/mediasoup.service.ts +++ b/apps/media/src/mediasoup/mediasoup.service.ts @@ -113,10 +113,6 @@ export class MediasoupService implements OnModuleInit { const peer = room.getPeer(socketId); const transport = peer.getTransport(transportId); - if (appData.mediaTypes !== 'audio') { - rtpParameters.encodings = server.PRODUCER_OPTIONS.encodings; - } - const producer = await transport.produce({ kind, rtpParameters, @@ -269,6 +265,8 @@ export class MediasoupService implements OnModuleInit { const peer = room.peers.get(socketId); const consumer = peer.getConsumer(consumerId); + if (!consumer) return; + consumer?.pause(); return { paused: true, consumerId, producerId: consumer.producerId }; @@ -279,6 +277,8 @@ export class MediasoupService implements OnModuleInit { const peer = room.peers.get(socketId); const consumer = peer.getConsumer(consumerId); + if (!consumer) return; + if (consumer?.producerPaused) { return { paused: true, consumerId, producerId: consumer.producerId }; } @@ -289,11 +289,15 @@ export class MediasoupService implements OnModuleInit { } pauseConsumers(socketId: string, roomId: string, consumerIds: string[]) { - return consumerIds.map((consumerId) => this.pauseConsumer(socketId, consumerId, roomId)); + return consumerIds + .map((consumerId) => this.pauseConsumer(socketId, consumerId, roomId)) + .filter(Boolean); } resumeConsumers(socketId: string, roomId: string, consumerIds: string[]) { - return consumerIds.map((consumerId) => this.resumeConsumer(socketId, consumerId, roomId)); + return consumerIds + .map((consumerId) => this.resumeConsumer(socketId, consumerId, roomId)) + .filter(Boolean); } changeConsumerPreferredLayers( diff --git a/packages/mediasoup/src/server/index.ts b/packages/mediasoup/src/server/index.ts index 43402b54..6e3afd84 100644 --- a/packages/mediasoup/src/server/index.ts +++ b/packages/mediasoup/src/server/index.ts @@ -77,14 +77,3 @@ export interface ChangeConsumerPreferredLayersDto { roomId: string; networkQualities: NetworkQualityDto[]; } - -export const PRODUCER_OPTIONS = { - encodings: [ - { rid: 'r0', maxBitrate: 50000, scalabilityMode: 'S1T3', active: true, dtx: false }, - { rid: 'r1', maxBitrate: 150000, scalabilityMode: 'S1T3', active: true, dtx: false }, - { rid: 'r2', maxBitrate: 500000, scalabilityMode: 'S1T3', active: true, dtx: false }, - ], - codecOptions: { - videoGoogleStartBitrate: 1000, - }, -}; From 715c98ac3cd634c3cc2b293d0f1a00194ce78b57 Mon Sep 17 00:00:00 2001 From: seoko97 Date: Thu, 5 Dec 2024 12:39:40 +0900 Subject: [PATCH 08/33] =?UTF-8?q?fix:=20consumer=EA=B0=80=20=EC=A2=85?= =?UTF-8?q?=EB=A3=8C=EB=90=9C=20=EA=B2=BD=EC=9A=B0=20=EC=97=85=EB=8D=B0?= =?UTF-8?q?=EC=9D=B4=ED=8A=B8=20=ED=95=98=EC=A7=80=20=EC=95=8A=EB=8F=84?= =?UTF-8?q?=EB=A1=9D=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/web/src/hooks/mediasoup/useRemoteStream.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/apps/web/src/hooks/mediasoup/useRemoteStream.ts b/apps/web/src/hooks/mediasoup/useRemoteStream.ts index fbf3e9b7..a1f15a4b 100644 --- a/apps/web/src/hooks/mediasoup/useRemoteStream.ts +++ b/apps/web/src/hooks/mediasoup/useRemoteStream.ts @@ -249,7 +249,7 @@ const useRemoteStream = () => { const newStreams = [...prevStreams]; const stream = newStreams.find((stream) => stream.consumer?.producerId === producerId); - if (!stream) { + if (!stream || stream.consumer?.closed) { return prevStreams; } @@ -280,7 +280,7 @@ const useRemoteStream = () => { const newStreams = [...prevStreams]; const stream = newStreams.find((stream) => stream.consumer?.producerId === producerId); - if (!stream) { + if (!stream || stream.consumer?.closed) { return prevStreams; } From b8d96162b3eb470dc67949e5be4a06f6431910a0 Mon Sep 17 00:00:00 2001 From: seoko97 Date: Thu, 5 Dec 2024 12:40:10 +0900 Subject: [PATCH 09/33] =?UTF-8?q?fix:=20=EB=84=A4=ED=8A=B8=EC=9B=8C?= =?UTF-8?q?=ED=81=AC=20=ED=92=88=EC=A7=88=20=EC=B2=B4=ED=81=AC=20=EA=B2=B0?= =?UTF-8?q?=EA=B3=BC=EA=B0=80=20=EC=97=86=EC=9D=84=20=EA=B2=BD=EC=9A=B0=20?= =?UTF-8?q?=EC=B2=98=EB=A6=AC=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/web/src/hooks/mediasoup/useNetworkMonitor.ts | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/apps/web/src/hooks/mediasoup/useNetworkMonitor.ts b/apps/web/src/hooks/mediasoup/useNetworkMonitor.ts index 7a842a01..5dcde660 100644 --- a/apps/web/src/hooks/mediasoup/useNetworkMonitor.ts +++ b/apps/web/src/hooks/mediasoup/useNetworkMonitor.ts @@ -13,7 +13,7 @@ const QUALITY_LEVEL = { }, poor: { quality: 0, - options: { packetLossRate: 5, jitter: 30, frameDropRate: 10, averageRTT: 300, nackCount: 50 }, + options: { packetLossRate: 10, jitter: 30, frameDropRate: 10, averageRTT: 300, nackCount: 50 }, }, } as const; @@ -144,6 +144,8 @@ const useNetworkMonitor = ({ streams }: UseNetworkMonitorProps) => { const networkQualities = await checkNetworkQuality(notPausedStreams); + if (!networkQualities || !networkQualities.length) return; + socket.emit(SOCKET_EVENTS.changeConsumerPreferredLayers, { roomId: ticleId, networkQualities, From bdf17ab6bb8fdab454c3dcd77bc9e2023e2c01ea Mon Sep 17 00:00:00 2001 From: seoko97 Date: Thu, 5 Dec 2024 12:40:32 +0900 Subject: [PATCH 10/33] =?UTF-8?q?fix:=20=EB=B9=84=EB=94=94=EC=98=A4=20?= =?UTF-8?q?=ED=94=84=EB=A1=9C=EB=93=80=EC=84=9C=20=EC=98=B5=EC=85=98?= =?UTF-8?q?=EC=9D=98=20=EC=9D=B8=EC=BD=94=EB=94=A9=20=EB=B0=8F=20=EC=BD=94?= =?UTF-8?q?=EB=8D=B1=20=EC=84=A4=EC=A0=95=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- packages/mediasoup/src/client/index.ts | 37 +++++++++++++++----------- 1 file changed, 22 insertions(+), 15 deletions(-) diff --git a/packages/mediasoup/src/client/index.ts b/packages/mediasoup/src/client/index.ts index 29c8cb40..a2622df1 100644 --- a/packages/mediasoup/src/client/index.ts +++ b/packages/mediasoup/src/client/index.ts @@ -65,25 +65,32 @@ export interface ResumeConsumersRes { paused: boolean; } -export const PRODUCER_OPTIONS: ProducerOptions = { - encodings: [ - { rid: 'r0', maxBitrate: 50000, scalabilityMode: 'S1T3' }, - { rid: 'r1', maxBitrate: 150000, scalabilityMode: 'S1T3' }, - { rid: 'r2', maxBitrate: 500000, scalabilityMode: 'S1T3' }, - ], - codecOptions: { - videoGoogleStartBitrate: 1000, - }, -}; - export const VIDEO_PRODUCER_OPTIONS: ProducerOptions = { encodings: [ - { rid: 'r0', maxBitrate: 50000, scalabilityMode: 'S1T3' }, - { rid: 'r1', maxBitrate: 150000, scalabilityMode: 'S1T3' }, - { rid: 'r2', maxBitrate: 500000, scalabilityMode: 'S1T3' }, + { + rid: 'r0', + maxBitrate: 750000, + scaleResolutionDownBy: 2, + scalabilityMode: 'S2T3', + maxFramerate: 30, + }, + { + rid: 'r1', + maxBitrate: 2000000, + scaleResolutionDownBy: 1.5, + scalabilityMode: 'S2T3', + maxFramerate: 30, + }, + { + rid: 'r2', + maxBitrate: 3000000, + scaleResolutionDownBy: 1, + scalabilityMode: 'S2T3', + maxFramerate: 30, + }, ], codecOptions: { - videoGoogleStartBitrate: 1000, + videoGoogleStartBitrate: 100000, opusDtx: true, }, }; From a86c8bd84e108a1273861f46f34604e7dc0e9df1 Mon Sep 17 00:00:00 2001 From: seoko97 Date: Thu, 5 Dec 2024 12:40:57 +0900 Subject: [PATCH 11/33] =?UTF-8?q?docs:=20README.md=20=EC=97=85=EB=8D=B0?= =?UTF-8?q?=EC=9D=B4=ED=8A=B8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index af8d7e9a..54abff5e 100644 --- a/README.md +++ b/README.md @@ -147,6 +147,7 @@ ## 프론트엔드 +

🔗 네트워크 상태에 따른 스트림 품질 변경

🔗 스트리밍을 최적화 해보자

> 최대한 많은 유저가 들어와 화상 서비스를 이용하는 것을 목적으로 하는 만큼 소켓 이벤트, 기존 할당된 자원에 대한 관리를 진행하며 최대한 적은 자원으로 나은 환경을 제공하기 위해 최적화를 진행하였습니다. From a706740c2c400541e8cf4012991f13438b50a19d Mon Sep 17 00:00:00 2001 From: seoko97 Date: Thu, 5 Dec 2024 13:58:44 +0900 Subject: [PATCH 12/33] =?UTF-8?q?fix:=20=EB=B9=84=EB=94=94=EC=98=A4=20?= =?UTF-8?q?=ED=94=84=EB=A1=9C=EB=93=80=EC=84=9C=20=EC=98=B5=EC=85=98?= =?UTF-8?q?=EC=9D=98=20=EC=B5=9C=EB=8C=80=20=EB=B9=84=ED=8A=B8=20=EC=A0=84?= =?UTF-8?q?=EC=86=A1=EB=A5=A0=20=EC=A1=B0=EC=A0=95=20=EB=B0=8F=20=EB=B6=88?= =?UTF-8?q?=ED=95=84=EC=9A=94=ED=95=9C=20scalabilityMode=20=EC=A0=9C?= =?UTF-8?q?=EA=B1=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- packages/mediasoup/src/client/index.ts | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/packages/mediasoup/src/client/index.ts b/packages/mediasoup/src/client/index.ts index a2622df1..cdeda50b 100644 --- a/packages/mediasoup/src/client/index.ts +++ b/packages/mediasoup/src/client/index.ts @@ -71,21 +71,18 @@ export const VIDEO_PRODUCER_OPTIONS: ProducerOptions = { rid: 'r0', maxBitrate: 750000, scaleResolutionDownBy: 2, - scalabilityMode: 'S2T3', maxFramerate: 30, }, { rid: 'r1', maxBitrate: 2000000, scaleResolutionDownBy: 1.5, - scalabilityMode: 'S2T3', maxFramerate: 30, }, { rid: 'r2', - maxBitrate: 3000000, + maxBitrate: 3500000, scaleResolutionDownBy: 1, - scalabilityMode: 'S2T3', maxFramerate: 30, }, ], From daba3502265c29f2b7a13753f5c46cd8e0d10074 Mon Sep 17 00:00:00 2001 From: seoko97 Date: Thu, 5 Dec 2024 14:25:58 +0900 Subject: [PATCH 13/33] =?UTF-8?q?fix:=20=EB=B6=88=ED=95=84=EC=9A=94?= =?UTF-8?q?=ED=95=9C=20scaleResolutionDownBy=20=EC=A0=9C=EA=B1=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- packages/mediasoup/src/client/index.ts | 3 --- 1 file changed, 3 deletions(-) diff --git a/packages/mediasoup/src/client/index.ts b/packages/mediasoup/src/client/index.ts index cdeda50b..3b748c97 100644 --- a/packages/mediasoup/src/client/index.ts +++ b/packages/mediasoup/src/client/index.ts @@ -70,19 +70,16 @@ export const VIDEO_PRODUCER_OPTIONS: ProducerOptions = { { rid: 'r0', maxBitrate: 750000, - scaleResolutionDownBy: 2, maxFramerate: 30, }, { rid: 'r1', maxBitrate: 2000000, - scaleResolutionDownBy: 1.5, maxFramerate: 30, }, { rid: 'r2', maxBitrate: 3500000, - scaleResolutionDownBy: 1, maxFramerate: 30, }, ], From 81a43aabfa958113e5aa127f7491ac26b4279817 Mon Sep 17 00:00:00 2001 From: seoko97 Date: Thu, 5 Dec 2024 14:26:18 +0900 Subject: [PATCH 14/33] =?UTF-8?q?fix:=20=ED=95=A8=EC=88=98=20=EC=9D=B4?= =?UTF-8?q?=EB=A6=84=20=EB=B3=80=EA=B2=BD=20=EB=B0=8F=20consumer=20?= =?UTF-8?q?=EC=83=81=ED=83=9C=20=ED=99=95=EC=9D=B8=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../web/src/hooks/mediasoup/useNetworkMonitor.ts | 16 ++++++++++++---- 1 file changed, 12 insertions(+), 4 deletions(-) diff --git a/apps/web/src/hooks/mediasoup/useNetworkMonitor.ts b/apps/web/src/hooks/mediasoup/useNetworkMonitor.ts index 5dcde660..acf5acb7 100644 --- a/apps/web/src/hooks/mediasoup/useNetworkMonitor.ts +++ b/apps/web/src/hooks/mediasoup/useNetworkMonitor.ts @@ -89,12 +89,12 @@ const useNetworkMonitor = ({ streams }: UseNetworkMonitorProps) => { [] ); - const checkNetworkQuality = async (streams: client.RemoteStream[]) => { + const checkNetworkQualities = async (streams: client.RemoteStream[]) => { const networkQualities = await Promise.all( streams.map(async (data) => { const { consumer } = data; - if (!consumer) return; + if (!consumer || consumer.closed || consumer.paused) return; let networkQuality = 2; // 0: poor, 1: average, 2: good @@ -131,7 +131,15 @@ const useNetworkMonitor = ({ streams }: UseNetworkMonitorProps) => { }) ); - return networkQualities; + return networkQualities.filter(Boolean).reduce( + (acc, cur) => { + if (!cur) return acc; + if (acc.some((data) => data.consumerId === cur.consumerId)) return acc; + + return [...acc, cur]; + }, + [] as { consumerId: string; networkQuality: number }[] + ); }; useEffect(() => { @@ -142,7 +150,7 @@ const useNetworkMonitor = ({ streams }: UseNetworkMonitorProps) => { const interval = setInterval(async () => { const notPausedStreams = getNotPausedStreams(streams); - const networkQualities = await checkNetworkQuality(notPausedStreams); + const networkQualities = await checkNetworkQualities(notPausedStreams); if (!networkQualities || !networkQualities.length) return; From 25e22e765d196ad8fbf84b4754a9c196dec79e1a Mon Sep 17 00:00:00 2001 From: seoko97 Date: Thu, 5 Dec 2024 14:44:10 +0900 Subject: [PATCH 15/33] =?UTF-8?q?fix:=20=EB=84=A4=ED=8A=B8=EC=9B=8C?= =?UTF-8?q?=ED=81=AC=20=EC=83=81=ED=83=9C=EC=97=90=20=EB=94=B0=EB=9D=BC=20?= =?UTF-8?q?=ED=95=B4=EC=83=81=EB=8F=84=EB=A7=8C=20=EB=B3=80=EA=B2=BD?= =?UTF-8?q?=ED=95=98=EB=8F=84=EB=A1=9D=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/media/src/mediasoup/mediasoup.service.ts | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/apps/media/src/mediasoup/mediasoup.service.ts b/apps/media/src/mediasoup/mediasoup.service.ts index 9cfcab4f..b80a67f9 100644 --- a/apps/media/src/mediasoup/mediasoup.service.ts +++ b/apps/media/src/mediasoup/mediasoup.service.ts @@ -311,10 +311,9 @@ export class MediasoupService implements OnModuleInit { const consumer = peer.getConsumer(consumerId); - consumer?.setPreferredLayers({ - spatialLayer: networkQuality, - temporalLayer: networkQuality, - }); + if (!consumer || consumer.closed || consumer.paused) return; + + consumer?.setPreferredLayers({ spatialLayer: networkQuality }); }); } From c654f4b51d194512348f5522f09a5f7a6af2d5d0 Mon Sep 17 00:00:00 2001 From: seoko97 Date: Thu, 5 Dec 2024 15:41:42 +0900 Subject: [PATCH 16/33] =?UTF-8?q?fix:=20=EB=AF=B8=EB=94=94=EC=96=B4=20?= =?UTF-8?q?=EC=9E=A5=EC=B9=98=20=ED=95=84=ED=84=B0=EB=A7=81=20=EB=A1=9C?= =?UTF-8?q?=EC=A7=81=20=EA=B0=9C=EC=84=A0=20=EB=B0=8F=20=EC=84=A0=ED=83=9D?= =?UTF-8?q?=EB=90=9C=20=EC=9E=A5=EC=B9=98=20ID=20=EC=B4=88=EA=B8=B0?= =?UTF-8?q?=ED=99=94=20=EC=A1=B0=EA=B1=B4=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/web/src/hooks/useMediaTracks.ts | 16 +++++++++++----- 1 file changed, 11 insertions(+), 5 deletions(-) diff --git a/apps/web/src/hooks/useMediaTracks.ts b/apps/web/src/hooks/useMediaTracks.ts index 41c7787c..187516e2 100644 --- a/apps/web/src/hooks/useMediaTracks.ts +++ b/apps/web/src/hooks/useMediaTracks.ts @@ -12,7 +12,7 @@ const DEFAULT_LOCAL_STREAM = { const getMediaDevices = (kind: MediaDeviceKind, devices: MediaDeviceInfo[]) => { return devices - .filter((device) => device.kind === kind && device.deviceId && device.deviceId !== 'default') + .filter((device) => device.kind === kind && device.deviceId) .map((device) => ({ label: device.label, value: device.deviceId })); }; @@ -116,16 +116,22 @@ const useMediaTracks = () => { setAudioDevices(audioInputs); setAudioOutputDevices(audioOutputs); - if (videoInputs[0]) setSelectedVideoDeviceId(videoInputs[0].value); - if (audioInputs[0]) setSelectedAudioDeviceId(audioInputs[0].value); - if (audioOutputs[0]) setSelectedAudioOutputDeviceId(audioOutputs[0].value); + if (videoInputs[0] && !selectedVideoDeviceId) { + setSelectedVideoDeviceId(videoInputs[0].value); + } + if (audioInputs[0] && !selectedAudioDeviceId) { + setSelectedAudioDeviceId(audioInputs[0].value); + } + if (audioOutputs[0] && !selectedAudioOutputDeviceId) { + setSelectedAudioOutputDeviceId(audioOutputs[0].value); + } } catch (_) { toast('미디어 정보를 가져올 수 없습니다.'); } }; fetchMediaDevices(); - }, []); + }, [video, audio]); return { video, From b66ff0667f99f6401f0ed6304c69c0563051413c Mon Sep 17 00:00:00 2001 From: seoko97 Date: Thu, 5 Dec 2024 15:41:51 +0900 Subject: [PATCH 17/33] =?UTF-8?q?fix:=20consumer=EC=9D=98=20=EC=84=A0?= =?UTF-8?q?=ED=98=B8=20=EB=A0=88=EC=9D=B4=EC=96=B4=20=EC=84=A4=EC=A0=95?= =?UTF-8?q?=EC=97=90=EC=84=9C=20null=20=EC=A1=B0=EA=B1=B4=EB=B6=80=20?= =?UTF-8?q?=EC=97=B0=EC=82=B0=EC=9E=90=20=EC=A0=9C=EA=B1=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/media/src/mediasoup/mediasoup.service.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/media/src/mediasoup/mediasoup.service.ts b/apps/media/src/mediasoup/mediasoup.service.ts index b80a67f9..6770fc49 100644 --- a/apps/media/src/mediasoup/mediasoup.service.ts +++ b/apps/media/src/mediasoup/mediasoup.service.ts @@ -313,7 +313,7 @@ export class MediasoupService implements OnModuleInit { if (!consumer || consumer.closed || consumer.paused) return; - consumer?.setPreferredLayers({ spatialLayer: networkQuality }); + consumer.setPreferredLayers({ spatialLayer: networkQuality }); }); } From 9af890b751db7386cf205944c99ee807ddbf82ae Mon Sep 17 00:00:00 2001 From: Eunseo Sim <55528304+simeunseo@users.noreply.github.com> Date: Thu, 5 Dec 2024 16:59:24 +0900 Subject: [PATCH 18/33] =?UTF-8?q?feat:=20AI=20=EC=9A=94=EC=95=BD=20dialog?= =?UTF-8?q?=20UI=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../src/components/dashboard/AiSummaryDialog.tsx | 13 +++---------- 1 file changed, 3 insertions(+), 10 deletions(-) diff --git a/apps/web/src/components/dashboard/AiSummaryDialog.tsx b/apps/web/src/components/dashboard/AiSummaryDialog.tsx index 70573089..ac761887 100644 --- a/apps/web/src/components/dashboard/AiSummaryDialog.tsx +++ b/apps/web/src/components/dashboard/AiSummaryDialog.tsx @@ -13,10 +13,10 @@ function AiSummaryDialog({ isOpen, onClose, ticleId }: AiSummaryDialogProps) { const { data } = useAiSummary(ticleId); return ( - + AI 음성 요약 - + {!data && (
@@ -25,15 +25,8 @@ function AiSummaryDialog({ isOpen, onClose, ticleId }: AiSummaryDialogProps) {
)} - {data && !data.summaryText && ( -
- - AI 요약 결과가 없어요. - -
- )} {data && data.summaryText && ( -

{data.summaryText}

+

{data.summaryText}

)}
From 99bdbc93cd1b5b7414363cf88534118d6fd5a9b6 Mon Sep 17 00:00:00 2001 From: LEE JIEUN Date: Thu, 5 Dec 2024 17:27:09 +0900 Subject: [PATCH 19/33] =?UTF-8?q?feat:=20=EB=8C=80=EC=8B=9C=EB=B3=B4?= =?UTF-8?q?=EB=93=9C=20=EC=A1=B0=ED=9A=8C=EC=8B=9C=EC=97=90=20summary=20?= =?UTF-8?q?=EC=97=AC=EB=B6=80=20=ED=8C=90=EB=8B=A8=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/api/src/dashboard/dashboard.module.ts | 3 ++- apps/api/src/dashboard/dashboard.service.ts | 30 ++++++++++++++++++--- 2 files changed, 28 insertions(+), 5 deletions(-) diff --git a/apps/api/src/dashboard/dashboard.module.ts b/apps/api/src/dashboard/dashboard.module.ts index 96823f0f..01d9017b 100644 --- a/apps/api/src/dashboard/dashboard.module.ts +++ b/apps/api/src/dashboard/dashboard.module.ts @@ -2,13 +2,14 @@ import { Module } from '@nestjs/common'; import { TypeOrmModule } from '@nestjs/typeorm'; import { Applicant } from '@/entity/applicant.entity'; +import { Summary } from '@/entity/summary.entity'; import { Ticle } from '@/entity/ticle.entity'; import { DashboardController } from './dashboard.controller'; import { DashboardService } from './dashboard.service'; @Module({ - imports: [TypeOrmModule.forFeature([Ticle, Applicant])], + imports: [TypeOrmModule.forFeature([Ticle, Applicant, Summary])], controllers: [DashboardController], providers: [DashboardService], }) diff --git a/apps/api/src/dashboard/dashboard.service.ts b/apps/api/src/dashboard/dashboard.service.ts index 78b89263..801be71c 100644 --- a/apps/api/src/dashboard/dashboard.service.ts +++ b/apps/api/src/dashboard/dashboard.service.ts @@ -4,6 +4,7 @@ import { Repository } from 'typeorm'; import { ErrorMessage, TicleStatus } from '@repo/types'; import { Applicant } from '@/entity/applicant.entity'; +import { Summary } from '@/entity/summary.entity'; import { Ticle } from '@/entity/ticle.entity'; @Injectable() @@ -12,7 +13,9 @@ export class DashboardService { @InjectRepository(Ticle) private readonly ticleRepository: Repository, @InjectRepository(Applicant) - private readonly applicantRepository: Repository + private readonly applicantRepository: Repository, + @InjectRepository(Summary) + private readonly summaryRepository: Repository ) {} async getCreatedTicleList( @@ -25,7 +28,15 @@ export class DashboardService { const queryBuilder = this.ticleRepository .createQueryBuilder('ticle') - .select(['ticle.id', 'ticle.title', 'ticle.startTime', 'ticle.endTime', 'ticle.ticleStatus']) + .leftJoin('ticle.summary', 'summary') + .select([ + 'ticle.id', + 'ticle.title', + 'ticle.startTime', + 'ticle.endTime', + 'ticle.ticleStatus', + 'summary.id', + ]) .where('ticle.speaker = :speakerId', { speakerId }) .skip(skip) .take(pageSize); @@ -40,7 +51,12 @@ export class DashboardService { } } - const [ticles, totalItems] = await queryBuilder.getManyAndCount(); + const [ticle, totalItems] = await queryBuilder.getManyAndCount(); + + const ticles = ticle.map((ticle) => ({ + ...ticle, + summary: ticle.summary ? ticle.summary.id !== null : false, + })); const totalPages = Math.ceil(totalItems / pageSize); @@ -62,6 +78,7 @@ export class DashboardService { const queryBuilder = this.applicantRepository .createQueryBuilder('applicant') .leftJoinAndSelect('applicant.ticle', 'ticle') + .leftJoin('ticle.summary', 'summary') .select([ 'applicant.id', 'ticle.id', @@ -70,6 +87,7 @@ export class DashboardService { 'ticle.startTime', 'ticle.endTime', 'ticle.ticleStatus', + 'summary.id', ]) .where('applicant.user = :userId', { userId }) .skip(skip) @@ -87,7 +105,11 @@ export class DashboardService { const [applicants, totalItems] = await queryBuilder.getManyAndCount(); - const ticles = applicants.map((applicant) => applicant.ticle); + const ticles = applicants.map((applicant) => ({ + ...applicant.ticle, + summary: applicant.ticle.summary ? applicant.ticle.summary.id !== null : false, // summary 필드 안전하게 처리 + })); + const totalPages = Math.ceil(totalItems / pageSize); return { From 38fae972ce4dce87b213044012223d8167146486 Mon Sep 17 00:00:00 2001 From: LEE JIEUN Date: Thu, 5 Dec 2024 17:47:12 +0900 Subject: [PATCH 20/33] =?UTF-8?q?feat:=20=ED=95=84=EC=9A=94=EC=97=86?= =?UTF-8?q?=EB=8A=94=20=EC=A3=BC=EC=84=9D=20=EC=82=AD=EC=A0=9C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/api/src/dashboard/dashboard.service.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/api/src/dashboard/dashboard.service.ts b/apps/api/src/dashboard/dashboard.service.ts index 801be71c..6bbdf8bb 100644 --- a/apps/api/src/dashboard/dashboard.service.ts +++ b/apps/api/src/dashboard/dashboard.service.ts @@ -107,7 +107,7 @@ export class DashboardService { const ticles = applicants.map((applicant) => ({ ...applicant.ticle, - summary: applicant.ticle.summary ? applicant.ticle.summary.id !== null : false, // summary 필드 안전하게 처리 + summary: applicant.ticle.summary ? applicant.ticle.summary.id !== null : false, })); const totalPages = Math.ceil(totalItems / pageSize); From b425bd3060e71ae47a8b1e212b8a3fd06cbbf2ea Mon Sep 17 00:00:00 2001 From: seoko97 Date: Thu, 5 Dec 2024 17:56:26 +0900 Subject: [PATCH 21/33] =?UTF-8?q?feat:=20NotSupportedMobile=20=ED=8E=98?= =?UTF-8?q?=EC=9D=B4=EC=A7=80=20=EC=BB=B4=ED=8F=AC=EB=84=8C=ED=8A=B8=20?= =?UTF-8?q?=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/web/src/components/NotSupportedMobile.tsx | 15 +++++++++++++++ 1 file changed, 15 insertions(+) create mode 100644 apps/web/src/components/NotSupportedMobile.tsx diff --git a/apps/web/src/components/NotSupportedMobile.tsx b/apps/web/src/components/NotSupportedMobile.tsx new file mode 100644 index 00000000..73c66030 --- /dev/null +++ b/apps/web/src/components/NotSupportedMobile.tsx @@ -0,0 +1,15 @@ +import TicleCharacterBadge from '@/assets/images/ticle-character-badge.png'; +import TicleLogo from '@/assets/ticle.svg?react'; + +function NotSupportedMobile() { + return ( +
+ 티클 캐릭터 + +

모바일 환경은 지원하지 않습니다

+

데스크톱 브라우저에서 접속해주세요.

+
+ ); +} + +export default NotSupportedMobile; From a1550402ac7a335fc3b0a490e384d38bf5fb7ecd Mon Sep 17 00:00:00 2001 From: seoko97 Date: Thu, 5 Dec 2024 17:56:35 +0900 Subject: [PATCH 22/33] =?UTF-8?q?feat:=20NotFound=20=ED=8E=98=EC=9D=B4?= =?UTF-8?q?=EC=A7=80=20=EC=BB=B4=ED=8F=AC=EB=84=8C=ED=8A=B8=20=EC=B6=94?= =?UTF-8?q?=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/web/src/components/NotFound.tsx | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) create mode 100644 apps/web/src/components/NotFound.tsx diff --git a/apps/web/src/components/NotFound.tsx b/apps/web/src/components/NotFound.tsx new file mode 100644 index 00000000..071993cf --- /dev/null +++ b/apps/web/src/components/NotFound.tsx @@ -0,0 +1,20 @@ +import { Link } from '@tanstack/react-router'; + +import TicleCharacterBadge from '@/assets/images/ticle-character-badge.png'; +import TicleLogo from '@/assets/ticle.svg?react'; +import Button from '@/components/common/Button'; + +function NotFound() { + return ( +
+ 티클 캐릭터 + +

페이지가 존재하지 않습니다

+ + + +
+ ); +} + +export default NotFound; From 0d2b935ee23c01fa3e465ca2611bff5e24592280 Mon Sep 17 00:00:00 2001 From: seoko97 Date: Thu, 5 Dec 2024 17:56:43 +0900 Subject: [PATCH 23/33] =?UTF-8?q?feat:=20=EB=AA=A8=EB=B0=94=EC=9D=BC=20?= =?UTF-8?q?=EC=A7=80=EC=9B=90=EB=90=98=EC=A7=80=20=EC=95=8A=EB=8A=94=20?= =?UTF-8?q?=EA=B2=BD=EC=9A=B0=20NotSupportedMobile=20=EC=BB=B4=ED=8F=AC?= =?UTF-8?q?=EB=84=8C=ED=8A=B8=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/web/src/routes/__root.tsx | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/apps/web/src/routes/__root.tsx b/apps/web/src/routes/__root.tsx index f3ef751b..0244f2c3 100644 --- a/apps/web/src/routes/__root.tsx +++ b/apps/web/src/routes/__root.tsx @@ -1,12 +1,19 @@ import { Outlet, ScrollRestoration, createRootRoute } from '@tanstack/react-router'; +import NotFound from '@/components/NotFound'; +import NotSupportedMobile from '@/components/NotSupportedMobile'; import { ToastContainer } from '@/components/toast/ToastContainer'; export const Route = createRootRoute({ component: RootComponent, + notFoundComponent: NotFound, }); function RootComponent() { + if (/iPhone|iPad|iPod|Android/i.test(navigator.userAgent)) { + return ; + } + return (
From 4b2da46a5f54247cca67d8fbd5e0e3a5b4aa0a99 Mon Sep 17 00:00:00 2001 From: Eunseo Sim <55528304+simeunseo@users.noreply.github.com> Date: Thu, 5 Dec 2024 18:10:32 +0900 Subject: [PATCH 24/33] =?UTF-8?q?feat:=20dashboardList=20response=EC=97=90?= =?UTF-8?q?=20summary=20=ED=95=84=EB=93=9C=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- packages/types/src/dashboard/getDashboardList.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/types/src/dashboard/getDashboardList.ts b/packages/types/src/dashboard/getDashboardList.ts index 0c1349db..a8c4dd26 100644 --- a/packages/types/src/dashboard/getDashboardList.ts +++ b/packages/types/src/dashboard/getDashboardList.ts @@ -36,6 +36,7 @@ const BaseDashboardResponseSchema = z.object({ startTime: z.string().datetime(), endTime: z.string().datetime(), ticleStatus: z.enum([TicleStatus.CLOSED, TicleStatus.OPEN, TicleStatus.IN_PROGRESS]), + summary: z.boolean(), }); const AppliedTicleSchema = BaseDashboardResponseSchema.extend({ From 1829eab4bc1a38ce097416588200d20917a4d0ff Mon Sep 17 00:00:00 2001 From: Eunseo Sim <55528304+simeunseo@users.noreply.github.com> Date: Thu, 5 Dec 2024 18:10:53 +0900 Subject: [PATCH 25/33] =?UTF-8?q?feat:=20summary=EA=B0=80=20=EC=A1=B4?= =?UTF-8?q?=EC=9E=AC=ED=95=A0=20=EB=95=8C=EB=A7=8C=20AI=20=EC=9A=94?= =?UTF-8?q?=EC=95=BD=20=EB=B2=84=ED=8A=BC=20=EB=B3=B4=EC=9D=B4=EA=B2=8C=20?= =?UTF-8?q?=ED=95=98=EA=B8=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../src/components/dashboard/apply/TicleInfoCard.tsx | 4 +++- apps/web/src/components/dashboard/apply/index.tsx | 1 + .../src/components/dashboard/open/TicleInfoCard.tsx | 12 ++++++++++-- apps/web/src/components/dashboard/open/index.tsx | 1 + 4 files changed, 15 insertions(+), 3 deletions(-) diff --git a/apps/web/src/components/dashboard/apply/TicleInfoCard.tsx b/apps/web/src/components/dashboard/apply/TicleInfoCard.tsx index 7371fe80..97fc140b 100644 --- a/apps/web/src/components/dashboard/apply/TicleInfoCard.tsx +++ b/apps/web/src/components/dashboard/apply/TicleInfoCard.tsx @@ -15,6 +15,7 @@ interface TicleInfoCardProps { startTime: string; endTime: string; status: 'closed' | 'open' | 'inProgress'; + isSummaryExist: boolean; } function TicleInfoCard({ @@ -24,6 +25,7 @@ function TicleInfoCard({ startTime, endTime, status, + isSummaryExist, }: TicleInfoCardProps) { const { isOpen, onOpen, onClose } = useModal(); const { dateStr, timeRangeStr } = formatDateTimeRange(startTime, endTime); @@ -59,7 +61,7 @@ function TicleInfoCard({
- {status === 'closed' && ( + {status === 'closed' && isSummaryExist && (
- {status === 'closed' && ( + {status === 'closed' && isSummaryExist && (