React Native 환경에서 WebRTC를 활용하여 실시간 영상 통신 기능을 구축하는 방법을 알아봅니다. 모바일 앱에서 피어 간 직접 연결을 구성하고 미디어 데이터를 교환하는 전체 프로세스를 다룹니다.
필수 컴포넌트 불러오기
라이브러리에서 필요한 클래스와 인터페이스를 가져옵니다:
import {
RTCView,
mediaDevices,
MediaStream,
RTCIceCandidate,
RTCSessionDescription,
RTCPeerConnection,
MediaStreamTrack,
registerGlobals,
ScreenCapturePickerView
} from 'react-native-webrtc';
각 구성요소의 역할:
mediaDevices: 카메라/마이크 접근 및 장치 관리RTCPeerConnection: 상대방과의 P2P 연결 관리RTCView: 로컬/원격 비디오 스트림 렌더링RTCSessionDescription/RTCIceCandidate: 연결 협상 정보
브라우저 호환성 설정
웹 환경과의 API 일관성이 필요한 경우 전역 등록을 수행합니다:
registerGlobals();
실행 후 navigator.mediaDevices 및 window.RTCPeerConnection 등이 전역에서 사용 가능합니다.
미디어 장치 탐색
사용 가능한 장치 조회
async function scanDevices() {
try {
const deviceList = await mediaDevices.enumerateDevices();
const videoDevices = deviceList.filter(
d => d.kind === 'videoinput'
);
console.log(`사용 가능한 카메라: ${videoDevices.length}대`);
return videoDevices;
} catch (err) {
console.error('장치 검색 오류:', err);
return [];
}
}
캡처 품질 설정
const captureConfig = {
audio: {
echoCancellation: true,
noiseSuppression: true
},
video: {
width: { ideal: 1280, min: 640 },
height: { ideal: 720, min: 480 },
frameRate: { ideal: 24, max: 30 },
facingMode: 'environment'
}
};
스트림 획득 및 관리
카메라 스트림 시작
let activeStream = null;
async function initializeCamera() {
try {
activeStream = await mediaDevices.getUserMedia(captureConfig);
// 오디오 전용 모드 전환 시
if (audioOnlyMode) {
activeStream.getVideoTracks().forEach(t => {
t.enabled = false;
});
}
} catch (err) {
console.error('카메라 접근 실패:', err);
}
}
화면 공유 스트림
async function beginScreenCapture() {
try {
activeStream = await mediaDevices.getDisplayMedia({
video: true,
audio: true
});
} catch (err) {
console.error('화면 공사 시작 실패:', err);
}
}
리소스 정리
function terminateStream() {
if (!activeStream) return;
activeStream.getTracks().forEach(track => {
track.stop();
});
activeStream = null;
}
P2P 연결 구성
ICE 서버 설정
const connectionConfig = {
iceServers: [
{ urls: 'stun:stun.l.google.com:19302' },
{ urls: 'stun:stun1.l.google.com:19302' }
],
iceCandidatePoolSize: 10
};
PeerConnection 인스턴스화
const pc = new RTCPeerConnection(connectionConfig);
pc.addEventListener('icecandidate', onIceCandidate);
pc.addEventListener('track', onRemoteTrack);
pc.addEventListener('connectionstatechange', onStateChange);
로컬 트랙 등록
function attachLocalMedia() {
activeStream.getTracks().forEach(track => {
pc.addTrack(track, activeStream);
});
}
시그널링 교환
Offer 생성 및 전송
async function generateOffer() {
try {
const offer = await pc.createOffer({
offerToReceiveAudio: true,
offerToReceiveVideo: true
});
await pc.setLocalDescription(offer);
signalingServer.emit('signal', {
type: 'offer',
payload: offer
});
} catch (err) {
console.error('Offer 생성 실패:', err);
}
}
Answer 수신 처리
async function processAnswer(remoteAnswer) {
try {
const desc = new RTCSessionDescription(remoteAnswer);
await pc.setRemoteDescription(desc);
} catch (err) {
console.error('원격 설명 설정 오류:', err);
}
}
데이터 채널 활용
채널 생성
const messageChannel = pc.createDataChannel('messaging', {
ordered: true,
maxRetransmits: 3
});
messageChannel.onopen = () => {
console.log('메시지 채널 활성화');
};
messageChannel.onmessage = ({ data }) => {
handleIncomingData(data);
};
메시지 전송
function transmitData(payload) {
if (messageChannel?.readyState === 'open') {
messageChannel.send(JSON.stringify(payload));
}
}
미디어 제어 기능
음소거 토글
function toggleAudioState() {
const audioTracks = activeStream.getAudioTracks();
audioTracks.forEach(track => {
track.enabled = !track.enabled;
});
return audioTracks[0]?.enabled ?? false;
}
카메라 전환
let currentCamera = 'environment';
async function flipCamera() {
const videoTrack = activeStream.getVideoTracks()[0];
const newFacing = currentCamera === 'user' ? 'environment' : 'user';
await videoTrack.applyConstraints({
facingMode: newFacing
});
currentCamera = newFacing;
}
UI 렌더링
<RTCView
streamURL={activeStream?.toURL()}
style={styles.preview}
objectFit="cover"
mirror={currentCamera === 'user'}
zOrder={2}
/>
<RTCView
streamURL={remoteMedia?.toURL()}
style={styles.remoteView}
objectFit="contain"
zOrder={1}
/>
운영 환경 고려사항
| 항목 | 권장 설정 |
|---|---|
| STUN/TURN | 코텔 서버 반드시 구성 |
| 권한 | Android: FOREGROUND_SERVICE, iOS: NSCameraUsageDescription |
| 배터리 | 화면 공유 시 Wake Lock 활성화 |
| 네트워크 | ICE 연결 상태 모니터링 및 재시도 로직 |
디버깅 체크포인트
pc.iceConnectionState:connected상태 확인pc.signalingState:stable도달 여부- 원격 스트림
ontrack이벤트 수신 타이밍 - ICE candidate 수집 완료 후 연결 시도
위 구성으로 기본적인 1:1 영상 통화 기능을 구현할 수 있습니다. 다인원 통신, SFU 구조, 또는 녹화 기능이 필요한 경우 추가 아키텍처 설계가 필요합니다.