WebRTC 화상통화 구현
WebRTC 화상통화 구현 — ConnectBase WebRTC API로 1:1 및 그룹 화상통화를 구현합니다
ConnectBase WebRTC API로 1:1 및 그룹 화상통화를 구현합니다
이 튜토리얼에서 배울 내용
WebRTC 화상통화 구현
WebRTC(Web Real-Time Communication)는 브라우저 간 직접 음성/영상을 주고받는 기술입니다. 하지만 직접 구현하려면 STUN/TURN 서버, 시그널링 서버, ICE candidate 교환 등 복잡한 과정이 필요합니다. ConnectBase WebRTC API를 사용하면 이 모든 과정을 SDK가 처리해줍니다.
완성된 기능
- WebRTC 연결 자동 초기화 (STUN/TURN 서버 설정 불필요)
- 카메라/마이크 미디어 스트림 획득
- 통화방 생성 및 참여
- 참가자 입장/퇴장 시 영상 스트림 자동 관리
사전 준비
- ConnectBase 콘솔에서 실시간 → WebRTC 서비스를 활성화합니다
- 왼쪽 메뉴에서 실시간을 클릭합니다
- WebRTC 탭에서 토글을 ON으로 켭니다
- 테스트 시 HTTPS 환경이 필요합니다 (카메라/마이크 권한은 HTTPS에서만 동작)
- 로컬 개발 시
localhost는 예외적으로 HTTP에서도 동작합니다
- 로컬 개발 시
1. 프로젝트 설정
npm create vite@latest video-call -- --template react-ts
cd video-call
npm install connectbase-client.env 파일을 만들고 Public Key를 입력합니다:
VITE_CONNECT_BASE_PUBLIC_KEY=cb_pk_여기에_퍼블릭키_입력
2. SDK 설정
src/lib/connectbase.ts:
import { ConnectBase } from 'connectbase-client'
export const cb = new ConnectBase({
publicKey: import.meta.env.VITE_CONNECT_BASE_PUBLIC_KEY,
appId: import.meta.env.VITE_APP_ID, // WebRTC 는 appId 가 필요합니다 (ICE 서버 조회용)
})3. WebRTC 훅 만들기
카메라/마이크 연결, 방 생성/참여, 참가자 관리를 하나의 훅으로 묶습니다.
SDK 구조:
cb.webrtc.connect({ roomId, userId, isBroadcaster, localStream })가 시그널링 WebSocket 연결을 담당합니다. 같은roomId로 connect 하면 자동으로 같은 방에 참여 — 별도createRoom/joinRoom메서드는 없으며roomId명명으로 구분합니다. 참가자 입장/퇴장은onPeerJoined/onPeerLeft콜백, 원격 스트림은onRemoteStream콜백으로 관리합니다.
src/hooks/useWebRTC.ts:
import { useState, useRef, useEffect } from 'react'
import { cb } from '../lib/connectbase'
interface RemotePeer { peerId: string; stream: MediaStream }
export function useWebRTC() {
const [peers, setPeers] = useState<RemotePeer[]>([])
const [connected, setConnected] = useState(false)
const localVideoRef = useRef<HTMLVideoElement>(null)
const localStreamRef = useRef<MediaStream | null>(null)
// 콜백 등록 — 컴포넌트 마운트 시 한 번 (반환된 unsubscribe 로 cleanup)
useEffect(() => {
const offJoined = cb.webrtc.onPeerJoined((peerId) => {
// peer 가 들어왔지만 stream 은 onRemoteStream 에서 받음
// 필요하면 여기에 정보만 미리 저장
})
const offStream = cb.webrtc.onRemoteStream((peerId, stream) => {
setPeers(prev => [...prev.filter(p => p.peerId !== peerId), { peerId, stream }])
})
const offLeft = cb.webrtc.onPeerLeft((peerId) => {
setPeers(prev => prev.filter(p => p.peerId !== peerId))
})
const offState = cb.webrtc.onStateChange((state) => {
setConnected(state === 'connected')
})
return () => { offJoined(); offStream(); offLeft(); offState() }
}, [])
// 카메라/마이크 켜기
const startMedia = async () => {
const stream = await navigator.mediaDevices.getUserMedia({ video: true, audio: true })
localStreamRef.current = stream
if (localVideoRef.current) localVideoRef.current.srcObject = stream
return stream
}
// 방에 참여 (같은 roomId 로 양쪽이 connect 하면 자동 매칭됨)
const join = async (roomId: string, userId: string) => {
const localStream = await startMedia()
await cb.webrtc.connect({
roomId, // 공유할 통화방 ID (예: 'call:abc-123')
userId, // 본인을 식별할 ID
isBroadcaster: true, // 영상/음성 송신자 — 모든 참가자가 송신하면 true
localStream,
})
}
// 연결 종료
const leave = () => {
cb.webrtc.disconnect()
localStreamRef.current?.getTracks().forEach(t => t.stop())
}
return { localVideoRef, peers, connected, join, leave }
}4. 화상통화 UI 만들기
내 영상과 다른 참가자들의 영상을 격자 레이아웃으로 표시합니다.
src/components/VideoCall.tsx:
import { useState } from 'react'
import { useWebRTC } from '../hooks/useWebRTC'
export function VideoCall() {
const { localVideoRef, peers, connected, join, leave } = useWebRTC()
const [roomId, setRoomId] = useState('demo-room-1')
const userId = 'user-' + Math.random().toString(36).slice(2, 8)
return (
<div>
<div style={{ marginBottom: 12 }}>
<input value={roomId} onChange={(e) => setRoomId(e.target.value)} />
<button onClick={() => join(roomId, userId)} disabled={connected}>참여</button>
<button onClick={leave} disabled={!connected}>나가기</button>
<span style={{ marginLeft: 8 }}>{connected ? '연결됨' : '대기'}</span>
</div>
{/* 비디오 격자 — 내 영상 + 다른 peer 영상 */}
<div style={{ display: 'grid', gridTemplateColumns: 'repeat(2, 1fr)', gap: 8 }}>
{/* 내 영상 — muted 로 에코 방지 */}
<video ref={localVideoRef} autoPlay muted playsInline style={{ width: '100%' }} />
{/* 원격 peer 영상 */}
{peers.map((p) => (
<video
key={p.peerId}
ref={(el) => { if (el) el.srcObject = p.stream }}
autoPlay
playsInline
style={{ width: '100%' }}
/>
))}
</div>
</div>
)
}💡 내 영상에
muted속성을 꼭 넣어야 합니다. 그렇지 않으면 내 마이크 소리가 내 스피커로 나와 에코가 발생합니다.
5. 실행하기
npm run dev- 브라우저에서 http://localhost:5173 을 엽니다
- 카메라/마이크 접근 허용 팝업이 뜨면 허용을 클릭합니다
- 방 ID 를 입력하고 "참여" 버튼을 클릭합니다
- 다른 브라우저 탭이나 기기에서 같은 방 ID 로 참여하여 테스트합니다
다음 단계
- 화면 공유 —
navigator.mediaDevices.getDisplayMedia()로 받은 스트림을connect({ localStream })에 전달 - 음소거/카메라 끄기 —
localStreamRef.current.getAudioTracks()[0].enabled = false - 에러 처리 —
cb.webrtc.onError((err) => ...)콜백으로 시그널링/ICE 실패 감지 - 수신 전용 모드 —
isBroadcaster: false+localStream생략 시 viewer 로만 참여 - 통화 종료 —
cb.webrtc.disconnect()호출 (위leave함수에서 이미 처리)
이 튜토리얼이 도움이 됐나요?