NextJS, Mediasoup, socketIO를 이용한 화상 회의 구현 3 ( consume 하기)

이진호·2024년 1월 18일
0

최종프로젝트

목록 보기
10/18
post-thumbnail

이전 글에 이어서 한 클라이언트가 producer를 하면 다른 유저는 그 내용을 consume을 하기 위해서 producer가 새로 생겼다는 내용을 socket을 통해서 전달을 한다. 그럼 produce를 하지 않은 다른 클라이언트는 'new-producer'라는 이벤트를 수신하게 되고 수신된 정보를 기반으로 새로운 recv consume을 만들어 달라고 서버에 요청을 한다.

마찬가지로 새로운 consumer를 만들기 위해서는 rtpCapabilities라는 정보가 필요하고 이 정보를 서버에 요청을 보내서 서버 쪽에서 먼저 consumer를 만들고 그다음에 다시 만들어진 정보를 기반으로 클라이언트 측에서도 consumer를 생성한다.

// server.js
socket.on(
  'transport-send-produce',
  () => {
    // 그 외 동일
    socket.emit('new-producer',producer.id, producer.appData);
    
})

새로운 producer가 생길 경우에 해당 producer의 아이디와 producer에 저장된 appData의 데이터를 다른 클라이언트에게 전달을 한다.

useEffect(() => {
  //...
  socket.on('new-producer',handleConsumeNewProducer);
  //
},[]);

function handleConsumeNewProducer(producerId: string, appData: AppData) {
  // 이미 존재하는 경우 넘어감
  if (isAlreadyConsume(consumers, producerId)) return;

  try {
    const rtpCapabilities = getRtpCapabilitiesFromDevice();

    socket.emit(
      "transport-recv-consume",
      { rtpCapabilities, producerId, appData, playerId: currentPlayerId },
      handleCreatedConsumer);
    // handle created consumer는 콜백함수이다. 핑퐁이 많기 때문에 의도적으로 함수로 분리를 했다.
}

'new-producer'이벤트를 수신받은 클라이언트는 producerId와 appData를 가지고 이미 consume하고 있는지를 확인을 하고 이미 consume하고 있지 않다면 서버 측에 consumer를 만들어 달라고 요청을 한다. 이때 rtp capabilities의 정보를 넘겨 준다.

//server.js
	socket.on(
      "transport-recv-consume",
      async ({ rtpCapabilities, producerId, appData, playerId }, callback) => {
        const client = clients[playerId];

        try {
          const recvTransport = client[RECV_TRANSPORT_KEY];

          if (!isCanConsumeWithRouter(producerId, rtpCapabilities)) {
            console.error("can not consume");
            return;
          }

          const consumer = await recvTransport.consume({
            producerId,
            rtpCapabilities,
            paused: true,
            appData,
          });

          consumer.on("transportclose", () => {
            recvTransport.close();
          });

          consumer.on("producerclose", () => {
            client.consumers = client.consumers.filter(
              (c) => c.id !== consumer.id
            );

            consumer.close();
          });

          const params = {
            id: consumer.id,
            producerId,
            kind: consumer.kind,
            rtpParameters: consumer.rtpParameters,
          };

          client.consumers = [...client.consumers, consumer];

          callback(params);
        } catch (error) {
          console.error("while consume recv transport error:", error);
        }
      }
    );

서버 측에서 정상적으로 consumer를 만든 이후에는 해당 내용을 다시 클라이언트에게도 보내서 클라이언트에서도 매칭될 수 있는 consumer를 만들 수 있도록 한다.

function handleCreatedConsumer(
	data : {
      id: string;
      producerId: string;
      kind: "audio" : "video";
      rtpParamters:RtpParameters;
    }
) {
	const { id, producerId, kind, rtpParameters } = data;

const consumer = await recvTransport.current?.consume({
  id,
  producerId,
  kind,
  rtpParameters,
  appData,
});

consumer?.on("@close", () => {
  console.log("basic close consumer");
});

consumer?.observer.on("close", () => {
  console.log("close consumer");
});

if (!consumer) {
  throw new Error("consumer가 없다...있어야 하는데...");
}

addConsumer(consumer);

socket.emit("consumer-resume", {
  consumerId: consumer.id,
  playerId: currentPlayerId,
});
}

클라이언트 측에서도 consumer를 만든 이후에 다시 소켓에 consumer-resume이라는 이벤트를 보내는데 해당 이벤트는 처음에 서버에서 만들 때 클라이언트에서 받고 난 이후에 정상적으로 consume을 하기 위해서 있는 코드로 서버 측 코드는 다음과 같다

socket.on('consumer-resume', ({consumerId, playerId}) => {
  try {
    const client = clients[playerId];
    const consumer = client.consumers.find((consumer) => consumer.id === consumerId);
    consumer.resume();
  } catch(error) {
    console.log(error);
  }
});

이렇게까지 모두 통신이 되면 정상적으로 연결이 되어 상대방이 공유한 화면을 볼 수 있다.

처음에는 엄청 난잡했는데 많이 코드를 보면서 어느정도 익숙해진 느낌이 크다. 그리고 스스로 최대한 직관적으로 리팩토링을 했다고 생각했는데 아직 더 할 요소들이 많은 것 같아서 반성하고 일단 기능 구현을 우선시 하고 코드를 계속해서 재정비 해나가야겠다.

profile
dygmm4288

0개의 댓글