From c4f33e67f662facef75ae8f84c6722b0bf98492f Mon Sep 17 00:00:00 2001
From: Wonjun Han <119842443+top-chaser@users.noreply.github.com>
Date: Thu, 7 Dec 2023 21:51:27 +0900
Subject: [PATCH] =?UTF-8?q?[feat]=20=EB=A7=A4=EB=8B=88=EC=A0=80=20?=
=?UTF-8?q?=EC=82=AD=EC=A0=9C=20=EB=B0=8F=20=EA=B0=95=ED=87=B4=20=EA=B8=B0?=
=?UTF-8?q?=EB=8A=A5=20=EA=B5=AC=ED=98=84=20(#172)?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
* refactor : 매니저 기능 삭제
* feat : 강퇴 기능 구현
* refactor : 리뷰 반영
* fix : url을 못가져오는 오류 수정
* feat : 방송 자동 실행하는 기능 구현
* style : lint 적용
---
.../components/HlsPlayer/HlsPlayer.styles.tsx | 15 ++++
client/src/components/HlsPlayer/HlsPlayer.tsx | 22 +-----
.../Modal/ViewerModal/ViewerModal.stories.tsx | 1 -
.../Modal/ViewerModal/ViewerModal.tsx | 79 +++++--------------
.../src/pages/BroadcastPage/BroadcastPage.tsx | 75 +++++++++---------
client/src/pages/MainPage/MainPage.tsx | 2 +-
server/api-server/src/chat/chat.gateway.ts | 18 +++++
.../src/chat/dto/kick-payload.dto.ts | 3 +
8 files changed, 97 insertions(+), 118 deletions(-)
create mode 100644 client/src/components/HlsPlayer/HlsPlayer.styles.tsx
create mode 100644 server/api-server/src/chat/dto/kick-payload.dto.ts
diff --git a/client/src/components/HlsPlayer/HlsPlayer.styles.tsx b/client/src/components/HlsPlayer/HlsPlayer.styles.tsx
new file mode 100644
index 0000000..ffcc03c
--- /dev/null
+++ b/client/src/components/HlsPlayer/HlsPlayer.styles.tsx
@@ -0,0 +1,15 @@
+import styled from 'styled-components'
+
+export const Container = styled.div`
+ position: relative;
+ padding-bottom: 56.25%;
+ padding-top: 9rem;
+`
+
+export const Video = styled.video`
+ position: absolute;
+ top: 0;
+ left: 0;
+ width: 100%;
+ height: 100%;
+`
diff --git a/client/src/components/HlsPlayer/HlsPlayer.tsx b/client/src/components/HlsPlayer/HlsPlayer.tsx
index ccf2797..5a3633b 100644
--- a/client/src/components/HlsPlayer/HlsPlayer.tsx
+++ b/client/src/components/HlsPlayer/HlsPlayer.tsx
@@ -1,6 +1,6 @@
import { useEffect, useRef } from 'react'
+import * as styles from './HlsPlayer.styles'
import Hls from 'hls.js'
-import styled from 'styled-components'
interface HlsPlayerProps {
id?: string
@@ -32,24 +32,10 @@ const HlsPlayer = ({ id }: HlsPlayerProps) => {
}, [])
return (
-
-
-
+
+
+
)
}
export default HlsPlayer
-
-const Container = styled.div`
- position: relative;
- padding-bottom: 56.25%;
- padding-top: 9rem;
-`
-
-const Video = styled.video`
- position: absolute;
- top: 0;
- left: 0;
- width: 100%;
- height: 100%;
-`
diff --git a/client/src/components/Modal/ViewerModal/ViewerModal.stories.tsx b/client/src/components/Modal/ViewerModal/ViewerModal.stories.tsx
index b10272c..e0e1973 100644
--- a/client/src/components/Modal/ViewerModal/ViewerModal.stories.tsx
+++ b/client/src/components/Modal/ViewerModal/ViewerModal.stories.tsx
@@ -20,7 +20,6 @@ export const Default: Story = {
authority: 'viewer',
target: 'viewer',
onCancle: () => {},
- onManager: () => {},
onKick: () => {},
currentTheme: ThemeFlag.light,
},
diff --git a/client/src/components/Modal/ViewerModal/ViewerModal.tsx b/client/src/components/Modal/ViewerModal/ViewerModal.tsx
index 8cc84b7..22bb594 100644
--- a/client/src/components/Modal/ViewerModal/ViewerModal.tsx
+++ b/client/src/components/Modal/ViewerModal/ViewerModal.tsx
@@ -3,19 +3,18 @@ import { ThemeFlag } from '@/types/theme'
interface ViewerModalProps {
nickname: string
- authority: 'viewer' | 'manager' | 'streamer'
- target: 'viewer' | 'manager' | 'streamer'
+ authority: 'viewer' | 'streamer'
+ target: 'viewer' | 'streamer'
top: number
left: number
onCancle: () => void
- onManager: (nickname: string) => void
- onKick: () => void
+ onKick: (nickname: string) => void
currentTheme: ThemeFlag
}
-const ViewerModal = ({ nickname, authority, target, top, left, onCancle, onManager, onKick, currentTheme }: ViewerModalProps) => {
+const ViewerModal = ({ nickname, authority, target, top, left, onCancle, onKick, currentTheme }: ViewerModalProps) => {
const getViewerModal = () => {
- if (authority === 'manager' && target === 'viewer') {
+ if (authority === 'streamer' && target === 'viewer') {
return (
{
@@ -26,65 +25,25 @@ const ViewerModal = ({ nickname, authority, target, top, left, onCancle, onManag
currentTheme={currentTheme}
>
{nickname}
-
+ onKick(nickname)} currentTheme={currentTheme}>
강퇴하기
)
- } else if (authority === 'streamer') {
- if (target === 'viewer') {
- return (
- {
- event.stopPropagation()
- }}
- top={top}
- left={left}
- currentTheme={currentTheme}
- >
- {nickname}
- onManager(nickname)} currentTheme={currentTheme}>
- 매니저로 지정하기
-
-
- 강퇴하기
-
-
- )
- } else if (target === 'manager') {
- return (
- {
- event.stopPropagation()
- }}
- top={top}
- left={left}
- currentTheme={currentTheme}
- >
- {nickname}
- onManager(nickname)} currentTheme={currentTheme}>
- 매니저 지정 해제하기
-
-
- 강퇴하기
-
-
- )
- }
+ } else {
+ return (
+ {
+ event.stopPropagation()
+ }}
+ top={top}
+ left={left}
+ currentTheme={currentTheme}
+ >
+ {nickname}
+
+ )
}
-
- return (
- {
- event.stopPropagation()
- }}
- top={top}
- left={left}
- currentTheme={currentTheme}
- >
- {nickname}
-
- )
}
return (
diff --git a/client/src/pages/BroadcastPage/BroadcastPage.tsx b/client/src/pages/BroadcastPage/BroadcastPage.tsx
index 40ffab7..73f736c 100644
--- a/client/src/pages/BroadcastPage/BroadcastPage.tsx
+++ b/client/src/pages/BroadcastPage/BroadcastPage.tsx
@@ -16,8 +16,8 @@ import HlsPlayer from '@components/HlsPlayer/HlsPlayer'
interface ViewerModalProps {
nickname: string
- authority: 'viewer' | 'manager' | 'streamer'
- target: 'viewer' | 'manager' | 'streamer'
+ authority: 'viewer' | 'streamer'
+ target: 'viewer' | 'streamer'
top: number
left: number
}
@@ -39,7 +39,6 @@ const BroadcastPage = () => {
const [loginModal, setLoginModal] = useState(false)
const [viewerModal, setViewerModal] = useState(false)
const [viewerModalInfo, setViewerModalInfo] = useState({ nickname: '', authority: 'viewer', target: 'viewer', top: 0, left: 0 })
- const [manager, setManager] = useState>([])
const [chatting, setChatting] = useState('')
const [chattingList, setChattingList] = useState>([])
const [confirmModal, setConfirmModal] = useState(false)
@@ -66,19 +65,13 @@ const BroadcastPage = () => {
setViewerModal(() => !viewerModal)
}
- const getTarget = (viewerNickname: string): 'viewer' | 'manager' | 'streamer' => {
- if (viewerNickname === streamer.nickname) {
- return 'streamer'
- } else if (manager.indexOf(viewerNickname) !== -1) {
- return 'manager'
- }
-
- return 'viewer'
+ const onKick = (viewerNickname: string) => {
+ socket.current.emit('chat', { nickname: viewerNickname })
}
const onNickname = (event: React.MouseEvent) => {
const viewerNickname = event.currentTarget.innerText
- const authority = 'streamer'
+ const authority = getAuthority()
const target = getTarget(viewerNickname)
const top = event.pageY
const left = event.pageX
@@ -87,18 +80,6 @@ const BroadcastPage = () => {
onViewer()
}
- const onManager = (viewerNickname: string) => {
- const index = manager.indexOf(viewerNickname)
-
- if (index === -1) {
- setManager([...manager, viewerNickname])
- } else {
- setManager(manager.filter((manager) => manager !== viewerNickname))
- }
-
- onViewer()
- }
-
const onChat = () => {
if (user.id === '') {
setConfirmModalMessage('로그인을 해주세요.')
@@ -116,18 +97,41 @@ const BroadcastPage = () => {
} else {
socket.current.emit('chat', { message: chatting })
}
-
setChatting('')
}
const onEnter = (event: React.KeyboardEvent) => {
if (event.key === 'Enter' && event.shiftKey === false) {
event.preventDefault()
-
onSend()
}
}
+ const onConfirm = () => {
+ setConfirmModal(false)
+ if (confirmModalMessage === '로그인을 해주세요.') {
+ onLogin()
+ } else if (confirmModalMessage === '방송 정보를 가져오는데 실패했습니다.' || confirmModalMessage === '방송에서 강퇴되었습니다.') {
+ window.location.replace('/')
+ }
+ }
+
+ const getAuthority = (): 'viewer' | 'streamer' => {
+ if (user.nickname === streamer.nickname) {
+ return 'streamer'
+ } else {
+ return 'viewer'
+ }
+ }
+
+ const getTarget = (viewerNickname: string): 'viewer' | 'streamer' => {
+ if (viewerNickname === streamer.nickname) {
+ return 'streamer'
+ } else {
+ return 'viewer'
+ }
+ }
+
const getStreamer = () => {
fetch(`${import.meta.env.VITE_API_URL}` + `/streams/${id}`, { method: 'GET', credentials: 'include' })
.then((res) => {
@@ -147,16 +151,6 @@ const BroadcastPage = () => {
})
}
- const onConfirm = () => {
- setConfirmModal(false)
-
- if (confirmModalMessage === '로그인을 해주세요.') {
- onLogin()
- } else if (confirmModalMessage === '방송 정보를 가져오는데 실패했습니다.') {
- window.location.replace('/')
- }
- }
-
useEffect(() => {
const interval = setInterval(() => {
getStreamer()
@@ -168,6 +162,12 @@ const BroadcastPage = () => {
socket.current.on('chat', (chatting: ChattingProps) => {
setChattingList((chattingList) => [chatting, ...chattingList])
})
+ socket.current.on('kick', (nickname: string) => {
+ if (user.nickname === nickname) {
+ setConfirmModalMessage('방송에서 강퇴되었습니다.')
+ setConfirmModal(true)
+ }
+ })
return () => {
if (socket.current) {
@@ -230,8 +230,7 @@ const BroadcastPage = () => {
top={viewerModalInfo.top}
left={viewerModalInfo.left}
onCancle={onViewer}
- onManager={onManager}
- onKick={onViewer}
+ onKick={onKick}
currentTheme={theme}
/>
)}
diff --git a/client/src/pages/MainPage/MainPage.tsx b/client/src/pages/MainPage/MainPage.tsx
index 6191665..0c914fb 100644
--- a/client/src/pages/MainPage/MainPage.tsx
+++ b/client/src/pages/MainPage/MainPage.tsx
@@ -67,7 +67,7 @@ const MainPage = () => {
nickname: broadcast.nickname,
title: `${broadcast.title === null ? `${broadcast.nickname}의 방송` : broadcast.title}`,
viewer: broadcast.viewer,
- thumbnail: broadcast.thumbnail.thumbnailurl,
+ thumbnail: broadcast.thumbnail.url,
}
}),
)
diff --git a/server/api-server/src/chat/chat.gateway.ts b/server/api-server/src/chat/chat.gateway.ts
index c0469df..2e6e864 100644
--- a/server/api-server/src/chat/chat.gateway.ts
+++ b/server/api-server/src/chat/chat.gateway.ts
@@ -10,6 +10,7 @@ import {
import { Server, Socket } from 'socket.io';
import { JoinPayload } from './dto/join-payload.dto';
import { ChatPayload } from './dto/chat-payload.dto';
+import { KickPayload } from './dto/kick-payload.dto';
import { Logger } from '@nestjs/common';
import { User } from 'src/users/entities/user.entity';
import { InjectRepository } from '@nestjs/typeorm';
@@ -78,6 +79,23 @@ export class ChatGateway implements OnGatewayConnection, OnGatewayDisconnect {
return payload;
}
+ @SubscribeMessage('kick')
+ async handleKickEvent(
+ @MessageBody() payload: KickPayload,
+ @ConnectedSocket() client: Socket,
+ ): Promise {
+ if (client.data.room !== client.data.userId) {
+ return;
+ }
+
+ this.logger.debug('Kick payload: ', payload);
+
+ this.server.to(client.data.room).emit('kick', {
+ nickname: payload.nickname,
+ });
+ return payload;
+ }
+
getViewers(room: string) {
return this.server.sockets.adapter.rooms.get(room)?.size || 0;
}
diff --git a/server/api-server/src/chat/dto/kick-payload.dto.ts b/server/api-server/src/chat/dto/kick-payload.dto.ts
new file mode 100644
index 0000000..4a839e4
--- /dev/null
+++ b/server/api-server/src/chat/dto/kick-payload.dto.ts
@@ -0,0 +1,3 @@
+export class KickPayload {
+ nickname: string;
+}