PROJECT

React.JS에서 openvidu 사용하기

JINGMONG 2022. 6. 21. 01:36

리액트에서 오픈비두를 사용하여 화상미팅을 구현하는 것은 매우 간단하다.

우선 오픈비두 라이브러리를 install한다.

npm i openvidu-browser

이러면 오픈비두를 사용할 모든 준비를 마쳤다. (여기서는 오픈비두 서버를 띄우는 것은 하지 않겠다)

오픈비두 라이브러리를 사용하여 화상 미팅을 구현하는 단계는 다음과 같다.

Publisher

OpenVidu 객체 생성 → session 생성 → token 생성 → user media 객체 생성 → publish

Subscriber

OpenVidu 객체 생성 → session 생성 → 메세지 수신 이벤트 정의 → 수신된 이벤트 처리

 

State

const [OV, setOV] = useState<OpenVidu>();
const [session, setSession] = useState<Session>();
const [initUserData, setInitUserData] = useState({
    mySessionId: channelId,
    myUserName: userInfo.nickname,
});
const [publisher, setPublisher] = useState<Publisher | null>(null);
const [subscribers, setSubscribers] = useState<Array<StreamManager>>([]);

Publish 과정

  1. OpenVidu 객체 생성
  2. Session 객체 생성
  3. state에 OpenVidu 객체와 Session 객체 담기 (unmount 되었을 때 session을 disconnect 하기 위해)
  4. connection 함수 정의
    1. token 생성
    2. user media 객체 생성
    3. publish
const joinSession = () => {
    // 1. openvidu 객체 생성
    const newOV = new OpenVidu();
    // socket 통신 과정에서 많은 log를 남기게 되는데 필요하지 않은 log를 띄우지 않게 하는 모드
    newOV.enableProdMode();
    // 2. initSesison 생성
    const newSession = newOV.initSession();

    // 3. 미팅을 종료하거나 뒤로가기 등의 이벤트를 통해 세션을 disconnect 해주기 위해 state에 저장
    setOV(newOV);
    setSession(newSession);

    // 4. session에 connect하는 과정
    const connection = () => {
      // 4-a token 생성
      getToken(initUserData.mySessionId!).then((token: any) => {
        newSession
          .connect(token, { clientData: initUserData.myUserName })
          .then(async () => {           
            // 4-b user media 객체 생성
            newOV
              .getUserMedia({
                audioSource: false,
                videoSource: undefined,
                resolution: '1280x720',
                frameRate: 10,
              })
              .then((mediaStream) => {
                var videoTrack = mediaStream.getVideoTracks()[0];

                var newPublisher = newOV.initPublisher(
                  initUserData.myUserName,
                  {
                    audioSource: undefined,
                    videoSource: videoTrack,
                    publishAudio: isAudioOn,
                    publishVideo: isVideoOn,
                    // resolution: '1280x720',
                    // frameRate: 10,
                    insertMode: 'APPEND',
                    mirror: true,
                  }
                );
                // 4-c publish
                newPublisher.once('accessAllowed', () => {
                  newSession.publish(newPublisher);
                  setPublisher(newPublisher);
                });
              });
          })
          .catch((error) => {
            console.warn(
              'There was an error connecting to the session:',
              error.code,
              error.message
            );
          });
      });
    };
    connection();
  };

getToken

const getToken = async (sessionId: string) => {
    return createSession(sessionId).then((sessionId: any) =>
      createToken(sessionId)
    );
  };

createSession

export const createSession = (sessionId: string) => {
  const data = JSON.stringify({ customSessionId: sessionId });
  return new Promise(async (resolve, reject) => {
    await openviduInstance
      .post(`/openvidu/api/sessions`, data)
      .then((response) => {
        resolve(response.data.id);
      })
      .catch((response) => {
        const err = Object.assign({}, response);
        if (err?.response?.status === 409) {
          resolve(sessionId);
        } else {
          console.warn(
            'No connection to OpenVidu Server. This may be a certificate error at ' +
              process.env.REACT_APP_OPENVIDU_SERVER_URL
          );
          if (
            window.confirm(
              'No connection to OpenVidu Server. This may be a certificate error at "' +
                process.env.REACT_APP_OPENVIDU_SERVER_URL +
                '"\\n\\nClick OK to navigate and accept it. ' +
                'If no certificate warning is shown, then check that your OpenVidu Server is up and running at "' +
                process.env.REACT_APP_OPENVIDU_SERVER_URL +
                '"'
            )
          ) {
            window.location.assign(
              process.env.REACT_APP_OPENVIDU_SERVER_URL + '/accept-certificate'
            );
          }
        }
      });
  });
};

createToken

export const createToken = (sessionId: string) => {
  const data = {};
  return new Promise(async (resolve, reject) => {
    await openviduInstance
      .post(`/openvidu/api/sessions/${sessionId}/connection`, data)
      .then((response) => {
        resolve(response.data.token);
      })
      .catch((error) => reject(error));
  });
};

getDevices 와 getUserMedia

미디어 정보를 담은 객체를 생성할 때 두가지 방법이 존재하였다.

getDevices 메소드를 사용하는 방법과 getUserMedia를 사용하는 방법이 있는데

getDevices 메소스를 사용하여 구현을 하였을 때 웹에서 카메라를 허용하는지 묻지 않는 문제가 있었다. 권한 설정에서 강제로 허용을 해주면 해결되는 문제였지만 사용자 입장에서 권한을 허용할지 묻는 것이 사용성이 더 뛰어날 것이라고 생각하여 getUserMedia 메소드를 사용하였다.

Subscribe 과정

subscribe 과정은 선언한 session에 메세지 수신 이벤트를 처리해주는 것만으로 간단하게 이루어진다.

  1. Session에 메세지 수신 이벤트 처리
  2. 수신한 이벤트에 따라 로직 구현
...
    const connection = () => {
      ...    
      // 1. connection 메소드 내부에 이벤트 수신 처리
      // 1-1 session에 참여한 사용자 추가
      newSession.on('streamCreated', (event) => {
        const newSubscriber = newSession.subscribe(
          event.stream,
          JSON.parse(event.stream.connection.data).clientData
        );
        
        const newSubscribers = subscribers;
        newSubscribers.push(newSubscriber);

        setSubscribers([...newSubscribers]);
      });
      // 1-2 session에서 disconnect한 사용자 삭제
      newSession.on('streamDestroyed', (event) => {
        if (event.stream.typeOfVideo === 'CUSTOM') {
          deleteSubscriber(event.stream.streamManager);
        } else {
          setDestroyedStream(event.stream.streamManager);
          setCheckMyScreen(true);
        }
      });
      // 1-3 예외처리
      newSession.on('exception', (exception) => {
        console.warn(exception);
      });
    ...
    }
...

deleteSubscriber

  const deleteSubscriber = (streamManager: StreamManager) => {
    const prevSubscribers = subscribers;
    let index = prevSubscribers.indexOf(streamManager, 0);
    if (index > -1) {
      prevSubscribers.splice(index, 1);
      setSubscribers([...prevSubscribers]);
    }
  };

'PROJECT' 카테고리의 다른 글

OpenVidu 라이브러리  (0) 2022.06.20
DOTORE 프로젝트 회고  (0) 2022.06.11
DRAWING DREAM 프로젝트 회고  (4) 2022.02.23