Skip to content

Commit

Permalink
[feat] 매니저 삭제 및 강퇴 기능 구현 (#172)
Browse files Browse the repository at this point in the history
* refactor : 매니저 기능 삭제

* feat : 강퇴 기능 구현

* refactor : 리뷰 반영

* fix : url을 못가져오는 오류 수정

* feat : 방송 자동 실행하는 기능 구현

* style : lint 적용
  • Loading branch information
Novrule authored Dec 7, 2023
1 parent 7dfb6d9 commit c4f33e6
Show file tree
Hide file tree
Showing 8 changed files with 97 additions and 118 deletions.
15 changes: 15 additions & 0 deletions client/src/components/HlsPlayer/HlsPlayer.styles.tsx
Original file line number Diff line number Diff line change
@@ -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%;
`
22 changes: 4 additions & 18 deletions client/src/components/HlsPlayer/HlsPlayer.tsx
Original file line number Diff line number Diff line change
@@ -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
Expand Down Expand Up @@ -32,24 +32,10 @@ const HlsPlayer = ({ id }: HlsPlayerProps) => {
}, [])

return (
<Container>
<Video ref={videoRef} controls />
</Container>
<styles.Container>
<styles.Video ref={videoRef} autoPlay muted controls />
</styles.Container>
)
}

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%;
`
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,6 @@ export const Default: Story = {
authority: 'viewer',
target: 'viewer',
onCancle: () => {},
onManager: () => {},
onKick: () => {},
currentTheme: ThemeFlag.light,
},
Expand Down
79 changes: 19 additions & 60 deletions client/src/components/Modal/ViewerModal/ViewerModal.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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 (
<styles.Modal
onClick={(event) => {
Expand All @@ -26,65 +25,25 @@ const ViewerModal = ({ nickname, authority, target, top, left, onCancle, onManag
currentTheme={currentTheme}
>
<styles.Nickname>{nickname}</styles.Nickname>
<styles.Content onClick={onKick} currentTheme={currentTheme}>
<styles.Content onClick={() => onKick(nickname)} currentTheme={currentTheme}>
강퇴하기
</styles.Content>
</styles.Modal>
)
} else if (authority === 'streamer') {
if (target === 'viewer') {
return (
<styles.Modal
onClick={(event) => {
event.stopPropagation()
}}
top={top}
left={left}
currentTheme={currentTheme}
>
<styles.Nickname>{nickname}</styles.Nickname>
<styles.Content onClick={() => onManager(nickname)} currentTheme={currentTheme}>
매니저로 지정하기
</styles.Content>
<styles.Content onClick={onKick} currentTheme={currentTheme}>
강퇴하기
</styles.Content>
</styles.Modal>
)
} else if (target === 'manager') {
return (
<styles.Modal
onClick={(event) => {
event.stopPropagation()
}}
top={top}
left={left}
currentTheme={currentTheme}
>
<styles.Nickname>{nickname}</styles.Nickname>
<styles.Content onClick={() => onManager(nickname)} currentTheme={currentTheme}>
매니저 지정 해제하기
</styles.Content>
<styles.Content onClick={onKick} currentTheme={currentTheme}>
강퇴하기
</styles.Content>
</styles.Modal>
)
}
} else {
return (
<styles.Modal
onClick={(event) => {
event.stopPropagation()
}}
top={top}
left={left}
currentTheme={currentTheme}
>
<styles.Nickname>{nickname}</styles.Nickname>
</styles.Modal>
)
}

return (
<styles.Modal
onClick={(event) => {
event.stopPropagation()
}}
top={top}
left={left}
currentTheme={currentTheme}
>
<styles.Nickname>{nickname}</styles.Nickname>
</styles.Modal>
)
}

return (
Expand Down
75 changes: 37 additions & 38 deletions client/src/pages/BroadcastPage/BroadcastPage.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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
}
Expand All @@ -39,7 +39,6 @@ const BroadcastPage = () => {
const [loginModal, setLoginModal] = useState<boolean>(false)
const [viewerModal, setViewerModal] = useState<boolean>(false)
const [viewerModalInfo, setViewerModalInfo] = useState<ViewerModalProps>({ nickname: '', authority: 'viewer', target: 'viewer', top: 0, left: 0 })
const [manager, setManager] = useState<Array<string>>([])
const [chatting, setChatting] = useState<string>('')
const [chattingList, setChattingList] = useState<Array<ChattingProps>>([])
const [confirmModal, setConfirmModal] = useState<boolean>(false)
Expand All @@ -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<HTMLInputElement>) => {
const viewerNickname = event.currentTarget.innerText
const authority = 'streamer'
const authority = getAuthority()
const target = getTarget(viewerNickname)
const top = event.pageY
const left = event.pageX
Expand All @@ -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('로그인을 해주세요.')
Expand All @@ -116,18 +97,41 @@ const BroadcastPage = () => {
} else {
socket.current.emit('chat', { message: chatting })
}

setChatting('')
}

const onEnter = (event: React.KeyboardEvent<HTMLTextAreaElement>) => {
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) => {
Expand All @@ -147,16 +151,6 @@ const BroadcastPage = () => {
})
}

const onConfirm = () => {
setConfirmModal(false)

if (confirmModalMessage === '로그인을 해주세요.') {
onLogin()
} else if (confirmModalMessage === '방송 정보를 가져오는데 실패했습니다.') {
window.location.replace('/')
}
}

useEffect(() => {
const interval = setInterval(() => {
getStreamer()
Expand All @@ -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) {
Expand Down Expand Up @@ -230,8 +230,7 @@ const BroadcastPage = () => {
top={viewerModalInfo.top}
left={viewerModalInfo.left}
onCancle={onViewer}
onManager={onManager}
onKick={onViewer}
onKick={onKick}
currentTheme={theme}
/>
)}
Expand Down
2 changes: 1 addition & 1 deletion client/src/pages/MainPage/MainPage.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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,
}
}),
)
Expand Down
18 changes: 18 additions & 0 deletions server/api-server/src/chat/chat.gateway.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand Down Expand Up @@ -78,6 +79,23 @@ export class ChatGateway implements OnGatewayConnection, OnGatewayDisconnect {
return payload;
}

@SubscribeMessage('kick')
async handleKickEvent(
@MessageBody() payload: KickPayload,
@ConnectedSocket() client: Socket,
): Promise<KickPayload> {
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;
}
Expand Down
3 changes: 3 additions & 0 deletions server/api-server/src/chat/dto/kick-payload.dto.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
export class KickPayload {
nickname: string;
}

0 comments on commit c4f33e6

Please sign in to comment.