220816 공통 프로젝트 개발일지

·2022년 8월 23일
0

개발일지 & 회고

목록 보기
19/72
post-thumbnail

예상 이슈 제어: 미팅 시 유저가 뒤로가기를 눌러 나오는 경우

먼저 현재 이루어지고 있는 다음 매칭 이벤트 부터 설명을 하자면, 유저가 현재 룸에서, 다음 매칭 버튼을 통해 나오는 경우에는 /meeting?rematching=true 와 같이 쿼리를 가지고 이동을 하게 된다. 단 경로 이동시에는, 리액트 라우터 이동이 아닌 window.location 을 통해 이동하도록 했다.

openvidu 객체를 다시 생성하고, 컴포넌트가 처음 만들어질 때 이벤트를 거는 useEffect()의 기능을 매 페이지 진입마다 사용하기 위해서이다.

문제는, 먼저 매칭이 이루어져, 한번 룸에 접근 했다가 상대방이 맘에 안들어 다음 매칭을 누른 뒤, 대화를 빠르게 종료하고자, 뒤로가기를 눌러 나왔다고 하자. 이 경우, 뒤로가기 경로는 /meeting?rematching=true를 가리키게 되고, 다시 재매칭이 이루어지는 현상이 나타나게 된다.

예상 이슈 제어 
rematching을 통해 방에 들어갔다가 뒤로가기를 통해 나오는 경우, /meeting?rematching=true로 이동하게 되고 다시 rematching이 이루어진다. 
더불어 뒤로가기를 통해 나가면 session 연결을 해제하는 이벤트를 건너뛰고 넘어오기 때문에, 뒤로가기를 클릭하게 되면 세션이 존재하는 경우, 지워버리게 하자.
  - /meeting에서 진입시 : history에 남겨서, 뒤로가기를 누르면 이동하게 해주자.
  - /meeting?rematching=true에서 진입시 : history에 남기지 않게 하여, 뒤로가기 이동시 /meeting?rematching=true 경로에 진입하지 않게한다.

하트 이벤트 : 하트 건네주기

먼저 mic, camera와 마찬가지로, 토글이 가능한 하트 버튼을 생성한다. 해당 하트가 true인 상태로, 세션이 정상적으로 종료되는 경우 (나가거나, 다음 매칭을 하지 않고, 0분 0초 까지 있는 경우) 상대방에게 하트가 건네진다.

import { useState, useRef } from 'react'

import {
  BsFillMicFill,
  BsFillMicMuteFill,
  BsFillCameraVideoFill,
  BsFillCameraVideoOffFill,
  BsStopwatch,
  BsHeartFill,
  BsHeart,
} from 'react-icons/bs'
import classes from './VideoControlBtns.module.css'

import Button from '../common/Button'

import PropTypes from 'prop-types'
import { useEffect } from 'react'
import { useDispatch, useSelector } from 'react-redux'
import { ovActions } from '../../store/ov-slice'
import { useNavigate } from 'react-router-dom'
import { leaveRoom } from '../../store/room-thunkActions'
import { peerUserActions } from '../../store/peerUser-slice'
import Sheet from '../common/Sheet'
import { getMyHeart, heartEvent } from '../../store/user-thunkActions'

const VideoControlBtns = ({ onLeaveSession, onToggleDevice, devices }) => {
  const [mic, setMic] = useState(true)
  const [camera, setCamera] = useState(false)
  const [heart, setHeart] = useState(false)
  const [minutes, setMinutes] = useState(5)
  const [seconds, setSeconds] = useState(0)

  const dispatch = useDispatch()
  const navigate = useNavigate()
  const auth = useSelector(state => state.auth)
  const room = useSelector(state => state.room)
  const user = useSelector(state => state.user)
  const peerUser = useSelector(state => state.peerUser)
  const openvidu = useSelector(state => state.openvidu)

  useEffect(() => {
    onToggleDevice(mic, camera)
  }, [mic, camera])

  // 초기 설정

  // 방 입장자가 연장을 요청하는 경우 생성자의 시간을 채운다.

  useEffect(() => {
    if (room.isCreatedRoom) {
      openvidu.session.on('signal:restore', () => {
        setMinutes(5)
        setSeconds(0)
      })
    }
    if (openvidu.session) {
      openvidu.session.on('signal:rematching', event => {
        const timeEvent = setTimeout(() => {
          dispatch(leaveRoom({ roomId: room.roomId }))
          dispatch(ovActions.leaveSession())
          window.location.replace('/meeting?rematching=true')
        }, 2000)
        return () => clearInterval(timeEvent)
      })
    } else {
      // 세션 없이 여기까지 오는 것은 비정상적 접근으로 판단 강제 페이지 이동
      navigate('/meeting', { replace: true })
    }
  }, [])

  // 마이크 설정
  const toggleMicHandler = () => setMic(prevMic => !prevMic)

  // 비디오 설정
  const toggleCameraHandler = () => setCamera(prevCamera => !prevCamera)

  // 하트 설정
  const toggleHeartHandler = () => setHeart(prevHeart => !prevHeart)

  const reMatchingUserHandler = () => {
    openvidu.publisher.session.signal({
      type: 'rematching',
    })
    // dispatch(leaveRoom({ roomId: room.roomId }))
    dispatch(ovActions.leaveSession())
    window.location.replace('/meeting?rematching=true')
  }

  const exitRoomHandler = () => {
    openvidu.publisher.session.signal({
      type: 'rematching',
    })
    // dispatch(leaveRoom({ roomId: room.roomId }))
    dispatch(ovActions.leaveSession())
    window.location.replace('/meeting')
  }

  const restoreTimeHandler = () => {
    if (room.isCreatedRoom) {
      if (user.subscribe) {
        setMinutes(5)
        setSeconds(0)
      } else if (user.heart >= 5) {
        const heartData = {
          cnt: -5,
          fromUser: user.id,
          name: 'extention',
          route: 'extention',
          toUser: 1,
        }
        dispatch(heartEvent({ accessToken: auth.token, heartData }))
        dispatch(getMyHeart(auth.token))
        setMinutes(5)
        setSeconds(0)
      }
    } else {
      if (user.subscribe) {
        openvidu.publisher.session.signal({
          type: 'restore',
        })
      } else if (user.heart >= 5) {
        const heartData = {
          cnt: -5,
          fromUser: user.id,
          name: 'extention',
          route: 'extention',
          toUser: 1,
        }
        dispatch(heartEvent({ accessToken: auth.token, heartData }))
        dispatch(getMyHeart(auth.token))
        openvidu.publisher.session.signal({
          type: 'restore',
        })
      }
    }
  }

  let openvidu_timer_minites = useRef()
  let openvidu_timer_seconds = useRef()

  useEffect(() => {
    // timer 로직 요약 설명
    // 생성자 -> 입장자 이루어지는 단방향 로직 설정
    // timer는 방 생성자가 연산하고 관리한다.
    // 방 생성자는 1초마다 연산한 timer를 제공
    // 방 입장자는 1초마다 제공되는 timer를 렌더링만 해야한다.

    // 방 생성자용 timer 설정
    const countdown = setInterval(async () => {
      if (room.isCreatedRoom) {
        const data = {
          minutes: minutes,
          seconds: seconds,
        }

        openvidu_timer_minites.current.textContent = minutes
        openvidu_timer_seconds.current.textContent =
          seconds < 10 ? `0${seconds}` : seconds

        openvidu.publisher.session.signal({
          data: JSON.stringify(data),
          type: 'timerShare',
        })

        if (seconds >= 0) {
          setSeconds(seconds - 1)
        }
        if (seconds === 0) {
          if (minutes === 0) {
            if (heart) {
              const heartData = {
                cnt: 1,
                fromUser: user.id,
                name: 'like',
                route: 'like',
                toUser: peerUser.id,
              }
              await dispatch(heartEvent({ accessToken: auth.token, heartData }))
            }
            clearInterval(countdown)
            dispatch(ovActions.leaveSession())
            window.location.replace('/meeting?rematching=true')
          } else {
            setMinutes(parseInt(minutes) - 1)
            setSeconds(59)
          }
        }
      } else {
        // 방 입장자용 timer 렌더 설정
        openvidu.session.on('signal:timerShare', event => {
          const data = JSON.parse(event.data)

          openvidu_timer_minites.current.textContent = data.minutes
          openvidu_timer_seconds.current.textContent =
            data.seconds < 10 ? `0${data.seconds}` : data.seconds

          setMinutes(data.minutes)
          setSeconds(data.seconds)
        })

        if (minutes === 0 && seconds === 0) {
          if (heart) {
            const heartData = {
              cnt: 1,
              fromUser: user.id,
              name: 'like',
              route: 'like',
              toUser: peerUser.id,
            }
            dispatch(heartEvent({ accessToken: auth.token, heartData }))
          }
          clearInterval(countdown)
          dispatch(ovActions.leaveSession())
          window.location.replace('/meeting?rematching=true')
        }
      }
    }, 1000)
    return () => clearInterval(countdown)
  }, [minutes, seconds, heart])

  // 상대방이 방에서 떠나는 경우 나도 나가지도록

  return (
    <div className={`flex_row_space_between ${classes.btns_wrapper}`}>
      <Sheet>
        <div className={`flex_row_center ${classes.icons}`}>
          <span onClick={toggleHeartHandler} className={classes.icon}>
            {heart ? <BsHeartFill /> : <BsHeart />}
          </span>
          <span onClick={toggleMicHandler} className={classes.icon}>
            {mic ? <BsFillMicFill /> : <BsFillMicMuteFill />}
          </span>
          <span onClick={toggleCameraHandler} className={classes.icon}>
            {camera ? <BsFillCameraVideoFill /> : <BsFillCameraVideoOffFill />}
          </span>
          <span className={classes.icon}>
            <BsStopwatch />
          </span>
          <span className={classes.timer}>
            <span ref={openvidu_timer_minites}></span>
            <span> : </span>
            <span ref={openvidu_timer_seconds}></span>
          </span>
          {user.subscribe || user.heart >= 5 ? (
            <Button
              size="small"
              text="시간연장"
              onEvent={restoreTimeHandler}
            ></Button>
          ) : (
            <Button size="small" text="시간연장" color="neutral"></Button>
          )}
        </div>
      </Sheet>
      <Sheet>
        <div className={`flex_row_center ${classes.matching_event}`}>
          <Button
            size="small"
            text="다음매칭"
            onEvent={reMatchingUserHandler}
          ></Button>
          <Button
            size="small"
            color="error"
            text="나가기"
            onEvent={exitRoomHandler}
          ></Button>
        </div>
      </Sheet>
    </div>
  )
}

VideoControlBtns.propTypes = {
  onLeaveSession: PropTypes.func,
  onToggleDevice: PropTypes.func,
  devices: PropTypes.object,
}

export default VideoControlBtns

하트 이벤트 : 하트 사용하여 시간 연장하기

하트를 사용하여 시간 연장도 가능하도록 구현했다. 로그인 시 하트의 개수를 가져온다. 만약, vip는 아니지만 하트가 5개 이상 있다면 시간연장 버튼 이벤트가 활성화 된다.

하트 이벤트 : 시간 연장
시간은 현재 방 생성자가 처리하여 시간을 공유하기 때문에, 방 생성자의 시간이 연장되면 자동으로 입장자의 시간도 연장된다.
- 방 생성자가 하트가 있어서 시간을 연장시키는 경우, 자신의 시간을 느리면 참여자도 그대로 반영이 된다.
- 방 참여자가 하트가 있어서 시간을 연장시키는 경우, 오픈비두 signal 함수를 통해 연결하여, 생성자의 시간을 늘리면 참여자에게도 그대로 반여이 된다.
profile
새로운 것에 관심이 많고, 프로젝트 설계 및 최적화를 좋아합니다.

0개의 댓글