공부한 내용/잘 쪼갤 수 있을까

잘 쪼갤 수 있을까(1)

hyongti 2022. 3. 16. 18:29

SettingPage랑 MainPage를 쪼개야 한다.

 

원래는 SettingPage에서 설정한 avatar, nickname, speakerDeviceId, micDeviceId를 쿼리스트링에 담아 MainPage로 이동하는 거였는데, 쿼리스트링에 담아서 보내면 링크를 복사해서 다른 사람에게 링크를 공유했을 때, 복사를 해서 접속한 사람이 공유해준 사람의 설정을 그대로 따라간다는 문제가 있다.

 

지금은 avatar랑 nickname은 리덕스를 통해 전역상태관리를 하게 바꿔놓은 상태고,

마이크와 스피커 장치를 선택하고 바꾸는 것도 전역으로 관리하고 싶다. 메인페이지에서도 마이크랑 스피커를 설정하는 컴포넌트가 필요해서 전역으로 관리할 필요가 있어보인다. 

(살짝 훑어보니 마이크 설정 변경 시, 이미 webRTC를 통해 연결된 peer들에게 보낼 stream을 바꾸는 로직 때문에 복잡해질 것 같긴 하다.)

(일단 무지성으로 리덕스를 선택했는데, recoil과 mobx와 같은 이름만 들어본 다른 전역상태관리 라이브러리도 고려를 해야할 것 같다.

일단은 참고할 게 많은 리덕스를 계속 이용하긴 해야겠다. 진도를 못 나가겠어서..)

 

현재 코드는 다음과 같다.

import React, {useEffect, useRef, useState} from 'react';
import {useNavigate, useSearchParams} from 'react-router-dom';
import {Button, message, Switch} from 'antd';
import {LeftCircleFilled, RightCircleFilled} from '@ant-design/icons';
import './setting.css';
import {avatarImageMDs} from '../../pixi/metaData/ImageMetaData';
import AudioVisualizer from './components/AudioVisualizer';
import SoundControl from './components/SoundControl';
import {getDeviceInfos, getAudioStreamFromDeviceID} from '../../audio/utils';

import {useSelector, useDispatch} from 'react-redux';
import Styled from './SettingPage.styled';
import {RootState} from '../../modules';
import useInput from '../../hooks/useInput';
import {changeAvatar, changeNickname} from '../../modules/profile';

function SettingPage(): JSX.Element {
  const navigate = useNavigate();
  // values
  const [searchParams] = useSearchParams();
  const [input, handleChange] = useInput('');
  const {nickname, avatar} = useSelector((state: RootState) => state.profile);
  const dispatch = useDispatch();

  //states
  const [deviceInfos, setDeviceInfos] = useState<MediaDeviceInfo[]>([]);
  const [selectedMicDeviceID, setSelectedMicDeviceID] = useState('default');
  const [selectedSpeakerDeviceID, setSelectedSpeakerDeviceID] =
    useState('default');
  const [audioStream, setAudioStream] = useState<MediaStream | null>(null);
  const [isLoading, setIsLoading] = useState(true);
  const [isListenMyMic, setIsListenMyMic] = useState(false);
  const [isSpeakerChangeable, setIsSpeakerChangeable] = useState(true);

  //ref
  const testAudioRef = useRef<HTMLAudioElement>(null);

  // functions
  const setSelectedMicDeviceAndSetAudioStream = (deviceID: string) => {
    setIsLoading(true);
    getAudioStreamFromDeviceID(deviceID)
      .then(audioStream => {
        setSelectedMicDeviceID(deviceID);
        setAudioStream(audioStream);
      })
      .catch(() => {
        message.error(
          '해당 장치 설정에 실패하였습니다. 장치 연결 상태를 확인하여 주세요.',
        );
      })
      .finally(() => {
        setIsLoading(false);
      });
  };

  const setSelectedSpeakerDeviceWithTest = (deviceID: string) => {
    setIsLoading(true);
    try {
      if (!testAudioRef.current) {
        throw 'testAudioRef is not found';
      }
      const audio = testAudioRef.current;
      // eslint-disable-next-line
      (audio as any)
        .setSinkId(deviceID)
        .then(() => {
          setSelectedSpeakerDeviceID(deviceID);
        })
        .catch(() => {
          message.error(
            '해당 장치 설정에 실패하였습니다. 장치 연결 상태를 확인하여 주세요.',
          );
        });
    } catch (error) {
      message.error(
        '해당 장치 설정에 실패하였습니다. 장치 연결 상태를 확인하여 주세요.',
      );
    } finally {
      setIsLoading(false);
    }
  };

  const reloadDeviceInfoClick = () => {
    setIsLoading(true);
    getDeviceInfos()
      .then(deviceInfos => {
        setDeviceInfos(deviceInfos);
        setSelectedMicDeviceID('default');
        setSelectedMicDeviceAndSetAudioStream('default');
        if (isSpeakerChangeable) {
          setSelectedSpeakerDeviceID('default');
          setSelectedSpeakerDeviceWithTest('default');
        }
      })
      .catch(() => {
        message.error(
          '장치를 재검색 하는데 실패하였습니다. 장치 연결 상태를 확인하여 주세요.',
        );
      })
      .finally(() => {
        setIsLoading(false);
      });
  };

  const enterClick = () => {
    dispatch(changeNickname(input));
    navigate(
      `/space?roomId=${searchParams.get(
        'roomId',
      )}&speakerDeviceID=${selectedSpeakerDeviceID}&micDeviceID=${selectedMicDeviceID}`,
    );
  };

  const avatarImgLeftOnClick = () => {
    dispatch(changeAvatar(-1));
  };

  const avatarImgRightOnClick = () => {
    dispatch(changeAvatar(1));
  };

  // useEffects
  useEffect(() => {
    const getAudioDevice = async () => {
      try {
        const audioStream = await getAudioStreamFromDeviceID('default');
        setAudioStream(audioStream);
        const deviceInfos = await getDeviceInfos();
        setDeviceInfos(deviceInfos);
        setIsLoading(false);
      } catch (e) {
        message.error(
          '음향 장치를 가져오는데 실패하였습니다. 장치 연결상태나 권한을 확인해 주세요.',
        );
      }
    };

    getAudioDevice();
  }, []);

  useEffect(() => {
    if (testAudioRef.current) {
      testAudioRef.current.srcObject = audioStream;
    }
    return () => {
      if (testAudioRef.current) {
        testAudioRef.current.srcObject = null;
      }
    };
  }, [audioStream]);

  useEffect(() => {
    if (testAudioRef.current) {
      // eslint-disable-next-line
      const audio = testAudioRef.current as any;
      if (audio.setSinkId) {
        audio.setSinkId(selectedSpeakerDeviceID);
      } else {
        setIsSpeakerChangeable(false);
      }
    }
  }, [selectedSpeakerDeviceID]);

  return (
    <Styled.SettingPageDiv>
      <div
        className="settingAvatarAndSoundOuterDiv"
        style={{
          width: 400,
          height: 400,
        }}
      >
        <div className="settingAvatarAndSoundinnerDiv">
          <div className="settingAvatarMainDiv">
            <div className="settingAvatarinnerDiv">
              <input
                value={input}
                placeholder="닉네임을 입력하세요."
                required
                onChange={handleChange}
                className="settingAvatarNameInput"
              ></input>
              <img
                className="settingAvatarImg"
                src={avatarImageMDs[avatar].avatarProfileSrc}
              ></img>
              <div className="settingAvatarButtonContainerDiv">
                <button
                  className="settingAvatarButton"
                  onClick={avatarImgLeftOnClick}
                >
                  <LeftCircleFilled></LeftCircleFilled>
                </button>
                <button
                  className="settingAvatarButton"
                  onClick={avatarImgRightOnClick}
                >
                  <RightCircleFilled></RightCircleFilled>
                </button>
              </div>
            </div>
          </div>
          <div className="SoundControlContainerDiv">
            <SoundControl
              deviceInfos={deviceInfos}
              selectedMicDeviceID={selectedMicDeviceID}
              selectedSpeakerDeviceID={selectedSpeakerDeviceID}
              setSelectedMicDeviceAndSetAudioStream={
                setSelectedMicDeviceAndSetAudioStream
              }
              setSelectedSpeakerDeviceWithTest={
                setSelectedSpeakerDeviceWithTest
              }
              isSpeakerDeviceChange={isSpeakerChangeable}
            ></SoundControl>
            <Button
              className="SoundControlReloadButton"
              onClick={reloadDeviceInfoClick}
              shape="round"
              loading={isLoading}
            >
              장치 재검색
            </Button>
            <Switch
              className="SoundControlMuteSwitch"
              checkedChildren="내 소리 듣는 중"
              unCheckedChildren="내 소리 안 듣는 중"
              defaultChecked={false}
              onChange={setIsListenMyMic}
            ></Switch>
            {audioStream ? (
              <AudioVisualizer audioStream={audioStream}></AudioVisualizer>
            ) : null}
          </div>
        </div>
        <div className="enterButtonContainerDiv">
          <Button
            className="enterButton"
            onClick={enterClick}
            shape="round"
            loading={isLoading}
          >
            입장하기
          </Button>
        </div>
      </div>
      <audio ref={testAudioRef} autoPlay={true} muted={!isListenMyMic}></audio>
    </Styled.SettingPageDiv>
  );
}

export default SettingPage;

 

오디오 장치 설정 변경을 위한 state가 지나치게 많고, 마이크랑 스피커를 바꾸는 함수가 너무 복잡해보인다. 마이크랑 스피커를 바꾸는 건 바꾸는대로 냅두고 그 결과를 통해 스트림을 바꾸고 싶다.

 

문제는 아직 리덕스랑 리덕스 미들웨어 사용이 능숙하지 않다는 것... 내일 열심히 쪼개봐야지..