
안녕하세요, 저번에 면접 세션 재연결 전략에 대해서 소개해드린 바 있는데, 이 때 발생했던 WebRTC 사용자간 미디어 스트림 끊김 현상을 해결한 과정을 소개드리겠습니다.

우선 WebRTC로 서로 간의 면접을 진행하다가 네트워크 장애가 발생하거나 사용자가 새로고침을 누르는 등의 행위를 하면 자동 재연결 프로세스를 통해 세션을 복구하게 됩니다.
다만, 세션이 복구되더라도 WebRTC를 통해 P2P로 주고 받던 미디어 스트림이 끊기는 문제가 발생했습니다.

사용자에게 흔히 발생하는 네트워크 변동과 새로 고침 상황에서 미디어 스트림이 끊기는 원인을 분석했습니다.
네트워크가 유선에서 무선 와이파이로 변하는 등의 상황에서는 기존의 ICE Candidate이 무효화되어 연결이 끊기게 됩니다.
새로고침 시 기존 PeerConnection 및 MediaStream 객체가 브라우저 컨텍스트에서 모두 소멸되며, 재마운트 시 새로운 객체로 생성되므로 기존 P2P 연결이 끊기게 됩니다.
또한, 동시에 부모 컴포넌트 상태 변경으로 인한 자식 컴포넌트의 리렌더링으로 인해 컴포넌트 리렌더링으로 초기화 로직이 경쟁적으로 실행되며 Race Condition도 존재하였습니다.
우선, 미디어 스트림을 관리하는 스토어에 비동기 작업을 공유하는 로직을 추가했습니다.
interface MediaStreamState {
	... 미디어 상태...
  // 준비 상태/에러
  isReady: boolean;
  error: string | null;
  // 내부 대기자
  _readyPromise: Promise<MediaStream> | null;
  _resolveReady?: (s: MediaStream) => void;
  _rejectReady?: (e: unknown) => void;
}
deferred promise 패턴을 사용하여 프로미스 외부에서 resovle와 reject를 할 수 있도록 createDeferred 함수를 만들었습니다.
function createDeferred<T>() {
  let resolve!: (v: T) => void;
  let reject!: (e: unknown) => void;
  const p = new Promise<T>((res, rej) => {
    resolve = res;
    reject = rej;
  });
  return { p, resolve, reject };
}
이후 미디어 스트림을 초기화 할 때 deffered promise 패턴을 사용하였습니다.
initMyStream: async () => {
      const { myStream, _readyPromise } = get();
      // 스트림이 이미 있으면 반환
      if (myStream) return myStream;
	  // 스트림을 가져오는 비동기 작업이 진행 중이면 프로미스 반환
      if (_readyPromise) return _readyPromise;
      // deferred promise 패턴을 사용하여 프로미스와 resolve, reject 생성
      const { p, resolve, reject } = createDeferred<MediaStream>();
      set({
        _readyPromise: p,
        _resolveReady: resolve,
        _rejectReady: reject,
        error: null,
      });
	  ... const stream = 미디어 가져오는 로직 ...
        // 스트림을 가져오면 resolve하여 대기 중인 호출자에게 스트림 반환
        resolve(stream);
        return p;
      } ... 에러 처리 ...
이로써 언마운트 / 앱 마운트 발생 시 리렌더링으로 인한 중복 호출 시에도 하나의 공유된 Promise 인스턴스를 상태에 유지함으로써, 중복 호출 시에도 동일한 비동기 결과를 참조하게 되어 초기화 과정이 일관성을 유지하게 됐습니다.

이후 사용자 간 기존 Candidate이 무효화되면 새롭게 수집한 Candidate을 교환하여 연결을 복원하였습니다.
새로운 탭으로 재접근 혹은 새로고침같은 언마운트 / 재마운트 상황에서도 서로의 미디어 스트림을 확인할 수 있게 되었고, WebRTC 재연결 로직을 추가하여 안정적인 서비스를 제공할 수 있었습니다.
