본문 바로가기

알고리즘

자바스크립트,react) video스트림 켜기(끈 상태)

비디오 스트림이란

자바스크립트에서 말하는 비디오 스트림이란 웹 브라우저에서 비디오 데이터를 스트리밍하는데 사용되는 개념이다.

일반적으로 웹캠이나 마이크로폰(마이크)과 같은 미디어 장치에서 생성된 미디어 데이터를 실시간으로 전송하거나(with P2P) 원격 서버에서 비디오 데이터를 다운로드하고 재생하는데 사용됩니다.

 

navigator.mediaDevices.getUserMedia({ video: true, audio: true })
  .then(function(stream) {
    // 비디오 스트림이 준비되면 처리
    // stream 객체를 비디오 요소나 오디오 요소의 srcObject에 할당하여 재생 가능
    videoElement.srcObject = stream;
  })
  .catch(function(error) {
    // 오류 처리
    console.error('미디어 스트림을 가져오는 중 오류 발생:', error);
  });

 

위와같이 비디오와 오디오 데이터를 stream이란 변수로 가져올 수 있다.

여기에

video.srcObject = stream;

을 추가해주면 비디오(오디오)스트림을 video 태그에 재생시킬 수 있다.

 


여기까지는 흔히 검색으로 나오는 내용이다.

그렇다면 오디오 스트림만 가져온 상태에서 비디오를 켜고싶다면 어떻게 해야할까.

가령 디스코드와 같이 말이다.

 

디스코드는 대화(오디오 대화)에 참여하는데 기본적으로 오디오 스트림만 제공한다.

추후 설정으로 웹캠으로 내 비디오를 p2p를 통해 실시간으로 전송시킬 수 있다.

 

단순히 내 비디오에 audio 스트림만 있는 상태에서 비디오스트림을 추가하는 것은 간단하다.

하지만 1:n 또는 n:n 상태의 p2p로 연결되어있다면 달라진다.

peer to peer로 이루어진 상태에서 단순히 스트림을 추가(또는 트랙 추가)하는 것만으로는 비디오 트랙이 추가된 내 스트림을 다른 사용자에게 실시간으로 보여줄 수없다.

 

addTrack 또는 addStream등 스트림에 데이터를 수정하는 것은 SDP에서의 변경 사항이 발생했다는 의미이다.

addTrack을 호출하면 새로운 미디오 트랙이 RTCPeerConnection에 추가되었다는 사실을 나타내는 SDP 교환이 필요하다. 이를 통해 두 피어 간에 합의된 미디오 설정이 항상 최신 상태로 유지되고 원활한 실시간 통신이 가능하다.

 

더 쉽게 말하면 addTrack을 사용해 트랙이 추가되었을 때, 이 변경 사항을 반영하고, 상호 간의 설정을 동기화하기 위해 SDP 재협상이 필요하다.

 

그럼 어떻게 해야할까

 

우선 MDN에서도 재협상 방법을 소개하고있다.

pc.addEventListener(
  "negotiationneeded",
  (ev) => {
    pc.createOffer()
      .then((offer) => pc.setLocalDescription(offer))
      .then(() =>
        sendSignalingMessage({
          type: "video-offer",
          sdp: pc.localDescription,
        }),
      )
      .catch((err) => {
        // handle error
      });
  },
  false,
);

 

하지만 무엇때문인지 이 이벤트리스너가 동작하지 않는다.

 

때문에 나는 수동으로 재협상을 시도했다. (대부분의 스택오버플로 답변에는 위 MDN방식을 따르던가, 또는 수동으로 재협상을 시도하라 말했다.)

 

우선 비디오를 켜는 버튼이 있고 그 버튼을 누르면 아래와 같이 video트랙와 audio트랙을 가져온다.

 

navigator.mediaDevices
        .getUserMedia({ video: true, audio: true })
        .then((stream) => {
        //내 비디오 태그에 연결
          myvideo.current.srcObject = stream;
          
          //peer에 연결된 유저
          Object.keys(peers.current).forEach(async (key) => {
          
          //유저의 RTCPeerConnection
            peers.current[key].peer.addStream(stream);
            const offer = await peers.current[key].peer.createOffer();
            peers.current[key].peer.setLocalDescription(offer);
            
            //재협상
            socketRef.current.emit(
              "renegotiate_offer",
              offer,
              myIdRef.current,
              key
            );
          });

 

각각의 유저들의 RTCPeerConnection(이하 peer)에 재협상을 알리는 offer를 생성한다.

 

socket.on("renegotiate_offer", async (offer, sender) => {

		//보낸 사람(비디오를 켠 사람)의 peer에 추가
        await peers.current[sender].peer.setRemoteDescription(offer);
        //답변 생성
        const answer = await peers.current[sender].peer.createAnswer();
        peers.current[sender].peer.setLocalDescription(answer);
        
        //재협상 수락
        socketRef.current.emit(
          "renegotiate_answer",
          answer,
          myIdRef.current,
          sender
        );
      });

 

 

이게 끝이다.

정말 쉽지않나?

 

코드로는 30줄이 안되지만 구글에 나와있는 정보가 다른 peer 연결 정보보다 훨씬 적어 해당 방법을 찾느라 무척이나 애를먹었다.