오르막길

[React+OpenVidu]완전 초보자가 OpenVidu를 React로 만져본 기록 본문

문제 해결하기/FE

[React+OpenVidu]완전 초보자가 OpenVidu를 React로 만져본 기록

nanalyee 2023. 1. 28. 17:51

webRTC(Web Real-Time Communication)

  • 웹 브라우저가 서로 통신할 수 있도록 설계된 API
  • 웹 브라우저 상에서는 어떠한 플러그인도 필요 없이 음성 채팅과 화상채팅, 데이터 교환까지도 가능하게 하는 기술
  • WebRTC 기술은 P2P(Peer-to-Peer) 통신에 최적화

 

Kurento란?

  • 전체 WebRTC 스택의 기능적 구현을 제공하는 미디어 서버
    (미디어 서버 : 압축(인코딩)된 동영상 파일들이 미디어서버로 들어오면 해석(디코딩) 한 후 사용자(Client) 가 볼 수 있게 재포장(압축) 해서 사용자의 동영상 Player 형태에 맞게 보내주는 역할)
  • HTTP, RTP, WebRTC를 포함한 네트워크 스트리밍 프로토콜 등 다양한 기능 지원

 

openVidu란?

  • kurento에 기반한 웹 또는 모바일 애플리케이션에서 화상 통화를 쉽게 사용 가능한 오픈 소스 플랫폼
  • OpenVidu Browser : 클라이언트 측에서 사용할 라이브러리
  • OpenVidu Server : 서버 측 내용 처리하는 어플리케이션
  • 화상 통화에 집중해 프로세스를 단순화하여 개발자는 클라이언트 측 코드를 커스터마이징해 구현할 수 있음
  • 즉, Kurento 라는 미디어 서버를 한번 더 래핑하여 사용자가 내부 구현을 몰라도 된다!
 

OpenVidu Docs

From here you can search these documents. Enter your search terms below.

docs.openvidu.io

 

왜 openvidu를 선택했는가?

직접 webRTC로 화상채팅을 구현한 팀들도 있었으나, 우리 팀은 화상채팅 외에도 다른 콘텐츠가 많았기때문에 최대한 빠르게 영상채팅 게임을 구현해야 했다.

그래서 비교적 단시간에 높은 퀄리티의 영상 채팅을 구현할 수 있는 openvidu의 힘을 빌리기로 했다.

 

 openVidu React을 실행해보자

openvidu-react - OpenVidu Docs

 

openvidu-react - OpenVidu Docs

From here you can search these documents. Enter your search terms below.

docs.openvidu.io

 

 

1️⃣ Run OpenVidu deployment

  1. 도커 설치가 먼저 진행되어야 한다!!
  2. powershell에 ~~docker run -p 4443:4443 --rm -e OPENVIDU_SECRET=MY_SECRET openvidu/openvidu-dev:2.25.0~~ 입력
  3. docker desktop에서 잘 돌아가고 있는지 확인

2️⃣ Run your preferred server application sample → node.js 버전을 사용했다

  1. git clone https://github.com/OpenVidu/openvidu-tutorials.git -b v2.25.0
  2. cd openvidu-tutorials/openvidu-basic-node
  3. npm install
  4. node index.js
  5. 백엔드 서버 구축 완료

3️⃣ Run the client application tutorial

  1. 2번과 다른 경로, 다른 bash에 openvidu-tutorials를 clone (같은 경로여도 되는 것 같다 나는 안해봄) https://github.com/OpenVidu/openvidu-tutorials
  2. cd openvidu-tutorials/openvidu-react
  3. npm install
  4. npm start
    error: 0308010C:digital envelope routines::unsupported
    ⇒ 구글링 결과 버전에러로 추정. package.json의 script 부분을 "start": "react-scripts --openssl-legacy-provider start", 로 변경
  5. 튜토리얼 끝!!

 

openVidu React를 간단하게 이해해보자

  • App.js: AppComponent , 앱의 주요 구성요소. 화상 통화에 참여하고 화상 통화 자체를 처리하는 기능이 포함되어 있습니다.
  • UserVideoComponent.js: UserVideoComponent , 모든 사용자 비디오를 표시하는 데 사용됩니다. 사용자 이름인 OpenViduVideoComponent 하나를 포함하고 AppComponent 보기를 업데이트하기 위한 클릭 이벤트도 처리합니다 .
  • OpenViduVideoComponent: 최종적으로 미디어 스트림을 표시하는 최종 HTML을 래핑하는 OpenViduVideoComponent 를 정의합니다.<video>

App.js 의 state

// These properties are in the state's component in order to re-render the HTML whenever their values change
this.state = {
    mySessionId: 'SessionA',
    myUserName: 'Participant' + Math.floor(Math.random() * 100),
    session: undefined,
    mainStreamManager: undefined, // Main video of the page. Will be the 'publisher' or one of the 'subscribers'
    publisher: undefined,
    subscribers: [],
};
  • OpenViduSessionobject 를 사용하면 바로 뒤에 선언되는 객체 를 얻을 수 있습니다. 
  • publisher자체 로컬 웹캠 스트림이 되고 subscribers배열은 화상 통화에서 다른 사용자의 활성 스트림을 저장합니다. 
  • mySessionId매개 myUserName변수는 잠시 후에 보게 되겠지만 화상 통화와 참가자의 닉네임을 나타냅니다.

Submit input을 클릭할 때마다 joinSession() 메서드 호출

  • 먼저 OpenVidu 개체를 가져오고 상태에서 세션 속성을 초기화합니다.
  • 관심 있는 세션 이벤트를 구독합니다. 
  • React 프레임워크를 사용하고 있으므로 원격 미디어 스트림을 관리하기 위한 좋은 접근 방식은 각 Subscriber개체에 공통 구성 요소를 공급하고 비디오를 관리하도록 하는 것입니다. => UserVideoComponent
  • 이렇게 하려면 받은 각 새 구독자를 배열에 저장 subscribers하고 필요할 때마다 삭제된 모든 구독자를 제거해야 합니다. 
  • streamCreated()
    • Session 개체가 수신한 각각의 새 Stream에 대해 구독하고 반환된 구독자 개체를 subscribers배열에 저장
    • AppComponent 의 HTML 템플릿은 각 구독자에 대해 UserVideoComponent 를 선언하는 .map js 함수를 포함하므로 새 비디오를 표시
    • 실제로 Subscriber객체로 공급하는 것이 아니라 부모 클래스로 공급 => StreamManager. 
    • 이렇게 하면 UserVideoComponentPublisher 를 재사용하여 객체(또한 StreamManager 클래스에서 상속됨)를 표시 할 수 있다.
  • streamDestroyed()
    • Session 개체에서 제거된 각 Stream에 대해(즉, 사용자가 화상 통화를 나갔다는 의미) 관련 Subscriber를 subscribers배열에서 제거하므로 React는 HTML에서 필요한 UserVideoComponent를 자동으로 삭제
    • 각 Stream 개체에는 streamManager소유한 구독자 또는 게시자를 나타내는 속성이 있다(동일한 방식으로 각 StreamManager 개체에도 해당 Stream에 대한 참조가 있음).
  • exception: 서버 측에서 예기치 않은 비동기 오류가 발생할 때 Session 개체에 의해 트리거되는 이벤트

OpenVidu 토큰 받기

액세스하려면 토큰이 필요하므로 서버 응용 프로그램 에 토큰을 요청, 그러면 서버 애플리케이션이 OpenVidu 배포에 대한 토큰을 요청

// --- 4) Connect to the session with a valid user token ---

// Get a token from the OpenVidu deployment
this.getToken().then((token) => {
    // See next point to see how to connect to the session using 'token'
}

 

애플리케이션 서버에서 최종적으로 토큰을 검색하는 역할을 하는 코드이다. 튜토리얼은 axios라이브러리를 사용하여 필요한 HTTP 요청을 수행

async getToken() {
    const sessionId = await this.createSession(this.state.mySessionId);
    return await this.createToken(sessionId);
}

async createSession(sessionId) {
    const response = await axios.post(APPLICATION_SERVER_URL + 'api/sessions', { customSessionId: sessionId }, {
        headers: { 'Content-Type': 'application/json', },
    });
    return response.data; // The sessionId
}

async createToken(sessionId) {
    const response = await axios.post(APPLICATION_SERVER_URL + 'api/sessions/' + sessionId + '/connections', {}, {
        headers: { 'Content-Type': 'application/json', },
    });
    return response.data; // The token
}

세션에 연결하고 웹캠 개시하기

// --- 4) Connect to the session with a valid user token ---

// Get a token from the OpenVidu deployment
this.getToken().then((token) => {
    // First param is the token got from the OpenVidu deployment. Second param can be retrieved by every user on event
    // 'streamCreated' (property Stream.connection.data), and will be appended to DOM as the user's nickname
    mySession.connect(token, { clientData: this.state.myUserName })
        .then(async () => {

            // --- 5) Get your own camera stream ---

            // Init a publisher passing undefined as targetElement (we don't want OpenVidu to insert a video
            // element: we will manage it on our own) and with the desired properties
            let publisher = await this.OV.initPublisherAsync(undefined, {
                audioSource: undefined, // The source of audio. If undefined default microphone
                videoSource: undefined, // The source of video. If undefined default webcam
                publishAudio: true, // Whether you want to start publishing with your audio unmuted or not
                publishVideo: true, // Whether you want to start publishing with your video enabled or not
                resolution: '640x480', // The resolution of your video
                frameRate: 30, // The frame rate of your video
                insertMode: 'APPEND', // How the video is inserted in the target element 'video-container'
                mirror: false, // Whether to mirror your local video or not
            });

            // --- 6) Publish your stream ---

            mySession.publish(publisher);

            // Obtain the current video device in use
            var devices = await this.OV.getDevices();
            var videoDevices = devices.filter(device => device.kind === 'videoinput');
            var currentVideoDeviceId = publisher.stream.getMediaStream().getVideoTracks()[0].getSettings().deviceId;
            var currentVideoDevice = videoDevices.find(device => device.deviceId === currentVideoDeviceId);

            // Set the main video in the page to display our webcam and store our Publisher
            this.setState({
                currentVideoDevice: currentVideoDevice,
                mainStreamManager: publisher,
                publisher: publisher,
            });
        })
        .catch((error) => {
            console.log('There was an error connecting to the session:', error.code, error.message);
        });
});
  • 방법이 성공하면 웹캠을 세션에 게시
  • Publisher는 원하는 속성을 가진 객체를 가져와 메서드를 통해 세션에 게시 => Session.publish()
  • 나머지 사용자는 Stream 개체를 받고 streamCreated이벤트를 실행
  • 마지막으로 기본 비디오 플레이어(다른 UserVideoComponent )가 기본적으로 Publisher 개체를 표시
  • 상태를 업데이트, 튜토리얼에서 사용자 카메라를 즉석에서 변경할 수 있으므로 현재 사용 중인 장치도 저장
  • 그렇게 OpenVidu.getDevices한 다음 비디오 장치를 필터링한 다음 deviceId속성을 사용하여 현재 사용 중인 장치 찾기
  • 현재 사용 중인 카메라를 this.state.currentVideoDevice및 게시자 개체를 this.state.publisher및 모두에 저장 this.state.mainStreamManager
  • 이렇게 하면 웹캠이 표시되는 것과 정확히 동일한 방식으로 모든 원격 구독자를 따라 추가

세션 나가기

사용자가 세션을 떠나기를 원할 때마다 다음 session.disconnect에서 메서드를 호출하기만 하면 됩니다.

  leaveSession() {

    // --- 7) Leave the session by calling 'disconnect' method over the Session object ---

    const mySession = this.state.session;

    if (mySession) {
        mySession.disconnect();
    }

    // Empty all properties...
    this.OV = null;
    this.setState({
         mySessionId: 'SessionA',
        myUserName: 'Participant' + Math.floor(Math.random() * 100),
        session: undefined,
        mainStreamManager: undefined,
        publisher: undefined,
        subscribers: [],
    });
  }