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; +}