@dany-kim/liverelay-sdk
v2.2.5
Published
LiveRelay SDK - Enterprise-grade real-time media streaming and communication platform
Maintainers
Readme
LiveRelay SDK 사용 가이드
화상회의 기능을 내 웹사이트에 추가하는 SDK입니다.
설치
npm install @dany-kim/liverelay-sdk라이브 데모
| 데모 | URL | 설명 |
|------|-----|------|
| 화상회의 (Zoom 스타일) | {서버주소}/sdk-demo | 방 생성/참여, 양방향 영상·음성·채팅 |
| 멀티뷰 (관제 모드) | {서버주소}/sdk-demo-multiview | 수신 전용 모니터링, 그리드 뷰, 스폿라이트 |
예:
https://rtc-insa.insahr.co.kr/sdk-demo
멀티뷰 데모 사용법:
- 멀티뷰에서 "방 만들기"로 방을 생성하면 공유 링크가 표시됩니다.
- 참가자들이 **화상회의 데모(
/sdk-demo)**에서 해당 코드로 입장합니다. - 참가자가 카메라를 켜면 멀티뷰 화면에 자동으로 영상이 나타납니다.
멀티뷰는 수신 전용이므로, 참가자가 최소 1명 이상 접속해야 영상을 확인할 수 있습니다.
1단계: 연결하기
import { LiveRelayClient } from '@dany-kim/liverelay-sdk';
// SDK 만들기
const client = new LiveRelayClient({
tenantId: 'my-company', // 우리 회사 ID
apiKey: 'sk_my_company_abc123', // 발급받은 API 키
serverUrl: 'https://rtc-insa.insahr.co.kr' // 서버 주소
});
// 서버에 연결
await client.connect();
console.log('서버 연결 성공!');2단계: 방 입장하기
const room = await client.joinRoom({
roomId: 'meeting-123', // 방 번호 (아무 문자열)
userName: '김철수', // 내 이름
userRole: 'participant' // 역할: 'admin' 또는 'participant'
});
console.log('방 입장 성공!', room.id);3단계: 카메라 켜기
// 카메라 켜기
await client.enableCamera();
console.log('카메라 켜짐!');
// 카메라 끄기
await client.disableCamera();4단계: 마이크 켜기
// 마이크 켜기
await client.enableMicrophone();
// 마이크 끄기
await client.disableMicrophone();5단계: 화면 공유
// 내 화면 공유 시작
await client.shareScreen();
// 화면 공유 중지
await client.stopScreenShare();6단계: 다른 사람 영상 보기
다른 사람이 카메라를 켜면 자동으로 알림이 옵니다.
// 누군가 영상을 보내기 시작하면
client.on('stream-added', ({ stream }) => {
// stream.stream이 MediaStream 객체
const videoElement = document.createElement('video');
videoElement.srcObject = stream.stream;
videoElement.autoplay = true;
videoElement.playsInline = true;
document.getElementById('videos').appendChild(videoElement);
});
// 영상이 끊기면
client.on('stream-removed', ({ streamId }) => {
console.log('영상 종료:', streamId);
});7단계: 채팅 보내기
// 전체 채팅
await client.sendPublicMessage('안녕하세요!');
// 특정 사람에게 귓속말
await client.sendPrivateMessage('비밀 메시지', '상대방ID');
// 채팅 받기
client.on('chat-message-received', ({ message }) => {
console.log(`${message.sender}: ${message.message}`);
});8단계: 참가자 관리
// 현재 참가자 목록 보기
const participants = client.getParticipants();
console.log('참가자:', participants);
// 누가 들어오면
client.on('participant-joined', ({ participant }) => {
console.log(`${participant.name}님이 입장했습니다`);
});
// 누가 나가면
client.on('participant-left', ({ participantId }) => {
console.log(`${participantId}님이 퇴장했습니다`);
});9단계: 방 나가기
await client.leaveRoom();
client.disconnect();
console.log('방을 나왔습니다');전체 예제 (복사해서 바로 사용)
HTML 파일 하나로 화상회의 만들기
<!DOCTYPE html>
<html lang="ko">
<head>
<meta charset="UTF-8">
<title>내 화상회의</title>
<style>
body { font-family: sans-serif; max-width: 800px; margin: 40px auto; }
#videos { display: flex; flex-wrap: wrap; gap: 10px; margin: 20px 0; }
#videos video { width: 300px; border-radius: 8px; background: #000; }
button { padding: 10px 20px; margin: 5px; border-radius: 6px; border: none; cursor: pointer; font-size: 14px; }
.btn-green { background: #22c55e; color: white; }
.btn-red { background: #ef4444; color: white; }
.btn-blue { background: #3b82f6; color: white; }
#chat { border: 1px solid #ddd; border-radius: 8px; padding: 10px; height: 200px; overflow-y: auto; margin: 10px 0; }
#chat p { margin: 4px 0; }
input { padding: 8px 12px; border: 1px solid #ddd; border-radius: 6px; }
</style>
</head>
<body>
<h1>내 화상회의</h1>
<!-- 1. 입장 폼 -->
<div id="login">
<input id="userName" placeholder="이름 입력" value="테스트유저" />
<input id="roomId" placeholder="방 번호" value="test-room" />
<button class="btn-blue" onclick="joinMeeting()">입장하기</button>
</div>
<!-- 2. 회의 화면 (처음엔 숨김) -->
<div id="meeting" style="display:none">
<p id="status">연결 중...</p>
<div>
<button class="btn-green" onclick="toggleCamera()">카메라 ON/OFF</button>
<button class="btn-green" onclick="toggleMic()">마이크 ON/OFF</button>
<button class="btn-blue" onclick="toggleScreen()">화면공유</button>
<button class="btn-red" onclick="leaveMeeting()">나가기</button>
</div>
<div id="videos">
<video id="localVideo" autoplay muted playsinline></video>
</div>
<div id="chat"></div>
<div>
<input id="chatInput" placeholder="메시지 입력..." onkeypress="if(event.key==='Enter') sendChat()" />
<button class="btn-blue" onclick="sendChat()">전송</button>
</div>
</div>
<script type="module">
import { LiveRelayClient } from '@dany-kim/liverelay-sdk';
// ===== 설정 (본인 서버 정보로 변경) =====
const SERVER_URL = 'https://rtc-insa.insahr.co.kr';
const TENANT_ID = 'my-company';
const API_KEY = 'sk_my_company_abc123';
let client = null;
let cameraOn = false;
let micOn = false;
let screenOn = false;
// 입장하기
window.joinMeeting = async function() {
const userName = document.getElementById('userName').value;
const roomId = document.getElementById('roomId').value;
if (!userName || !roomId) {
alert('이름과 방 번호를 입력하세요');
return;
}
try {
// 1. SDK 생성 + 연결
client = new LiveRelayClient({
tenantId: TENANT_ID,
apiKey: API_KEY,
serverUrl: SERVER_URL
});
await client.connect();
// 2. 방 입장
await client.joinRoom({ roomId, userName, userRole: 'participant' });
// 3. 이벤트 등록
setupEvents();
// 4. 화면 전환
document.getElementById('login').style.display = 'none';
document.getElementById('meeting').style.display = 'block';
document.getElementById('status').textContent = `${roomId} 방에 입장했습니다 (${userName})`;
} catch (error) {
alert('입장 실패: ' + error.message);
}
};
// 이벤트 등록
function setupEvents() {
// 다른 사람 영상이 도착하면
client.on('stream-added', ({ stream }) => {
const video = document.createElement('video');
video.id = 'remote-' + stream.id;
video.srcObject = stream.stream;
video.autoplay = true;
video.playsInline = true;
document.getElementById('videos').appendChild(video);
});
// 영상이 끊기면
client.on('stream-removed', ({ streamId }) => {
const video = document.getElementById('remote-' + streamId);
if (video) video.remove();
});
// 채팅 수신
client.on('chat-message-received', ({ message }) => {
const chat = document.getElementById('chat');
const p = document.createElement('p');
const sender = message.senderName || message.sender || '알수없음';
const text = message.content || message.message || '';
p.textContent = `${sender}: ${text}`;
chat.appendChild(p);
chat.scrollTop = chat.scrollHeight;
});
// 참가자 입장/퇴장
client.on('participant-joined', ({ participant }) => {
addChatLine(`[${participant.name}님이 입장했습니다]`);
});
client.on('participant-left', ({ participantId }) => {
addChatLine(`[${participantId}님이 퇴장했습니다]`);
});
}
function addChatLine(text) {
const chat = document.getElementById('chat');
const p = document.createElement('p');
p.style.color = '#888';
p.textContent = text;
chat.appendChild(p);
}
// 카메라 켜기/끄기
window.toggleCamera = async function() {
if (!client) return;
if (cameraOn) {
await client.disableCamera();
document.getElementById('localVideo').srcObject = null;
} else {
await client.enableCamera();
// 로컬 비디오 표시
const stream = client.getLocalStream('camera');
if (stream) {
document.getElementById('localVideo').srcObject = stream;
}
}
cameraOn = !cameraOn;
};
// 마이크 켜기/끄기
window.toggleMic = async function() {
if (!client) return;
if (micOn) {
await client.disableMicrophone();
} else {
await client.enableMicrophone();
}
micOn = !micOn;
};
// 화면공유
window.toggleScreen = async function() {
if (!client) return;
if (screenOn) {
await client.stopScreenShare();
} else {
await client.shareScreen();
}
screenOn = !screenOn;
};
// 채팅 전송
window.sendChat = async function() {
const input = document.getElementById('chatInput');
const text = input.value.trim();
if (!text || !client) return;
await client.sendPublicMessage(text);
addChatLine(`나: ${text}`);
input.value = '';
};
// 나가기
window.leaveMeeting = async function() {
if (client) {
await client.leaveRoom();
client.disconnect();
client = null;
}
document.getElementById('meeting').style.display = 'none';
document.getElementById('login').style.display = 'block';
};
</script>
</body>
</html>이벤트 목록 (전체)
| 이벤트 | 언제 발생하나 | 받는 데이터 |
|--------|-------------|-------------|
| connection-state-changed | 연결 상태가 바뀔 때 | { state, reason } |
| room-joined | 방에 들어갔을 때 | { room } |
| room-left | 방에서 나왔을 때 | { roomId, reason } |
| participant-joined | 다른 사람이 들어왔을 때 | { participant } |
| participant-left | 다른 사람이 나갔을 때 | { participantId } |
| stream-added | 영상/오디오가 도착했을 때 | { stream } |
| stream-removed | 영상/오디오가 끊겼을 때 | { streamId } |
| chat-message-received | 채팅 메시지를 받았을 때 | { message } |
| chat-permissions-changed | 채팅 권한이 바뀔 때 | { permissions } |
| local-media-changed | 내 카메라/마이크 상태 변경 | { type, enabled } |
| error | 오류 발생 | { error } |
주요 메서드 목록
연결
| 메서드 | 설명 |
|--------|------|
| connect() | 서버에 연결 |
| disconnect() | 서버 연결 끊기 |
| joinRoom(config) | 방 입장 |
| leaveRoom() | 방 나가기 |
| testConnection() | 연결 품질 테스트 (지연시간/품질 반환) |
| getConnectionState() | 연결 상태 조회 (connected, disconnected, reconnecting 등) |
미디어
| 메서드 | 설명 |
|--------|------|
| enableCamera(constraints?) | 카메라 켜기 (선택적 제약 조건) |
| disableCamera() | 카메라 끄기 |
| enableMicrophone(constraints?) | 마이크 켜기 (선택적 제약 조건) |
| disableMicrophone() | 마이크 끄기 |
| shareScreen(options?) | 화면공유 시작 |
| stopScreenShare() | 화면공유 중지 |
| getLocalStream(type) | 내 스트림 가져오기 ('camera', 'microphone', 'screen') |
| getRemoteStreams() | 원격 참가자 스트림 목록 조회 |
| subscribeToParticipant(id) | 특정 참가자 스트림 구독 |
| unsubscribeFromParticipant(id) | 특정 참가자 스트림 구독 해제 |
| startRecording(options?) | 클라이언트 사이드 녹화 시작 |
| stopRecording(recordingId) | 클라이언트 사이드 녹화 중지 |
채팅
| 메서드 | 설명 |
|--------|------|
| sendPublicMessage(text) | 전체 채팅 |
| sendPrivateMessage(text, targetId) | 귓속말 |
| sendMessage(content, type, targetId?) | 통합 메시지 전송 |
| getChatHistory() | 채팅 기록 가져오기 |
| searchMessages(query) | 채팅 메시지 검색 |
참가자
| 메서드 | 설명 |
|--------|------|
| getParticipants() | 참가자 목록 |
| getLocalParticipant() | 내 정보 |
| getParticipant(id) | 특정 참가자 정보 조회 |
| getConnectedParticipants() | 연결된 참가자만 |
| getAdmins() | 관리자 목록 조회 |
관리자 전용
| 메서드 | 설명 |
|--------|------|
| removeParticipant(id, reason) | 참가자 강퇴 |
| changeParticipantRole(id, role) | 역할 변경 |
상태/통계
| 메서드 | 설명 |
|--------|------|
| getCurrentRoom() | 현재 룸 정보 조회 |
| getConnectionStats() | 연결 통계 (대역폭, 지연 등) |
| getMediaState() | 미디어 상태 (카메라/마이크/화면공유 ON/OFF) |
| getParticipantStats() | 참가자 통계 |
| getSDKStats() | SDK 전체 통계 (연결, 미디어, 성능, 세션) |
| getSocket() | 소켓 인스턴스 직접 접근 (화이트보드/소모임 등 확장 기능용) |
유틸리티
| 메서드 | 설명 |
|--------|------|
| getVersion() | SDK 버전 조회 |
| getInstanceId() | 인스턴스 ID 조회 |
| getConfig() | 설정 정보 조회 |
| destroy() | 리소스 정리 (녹화 중지 → 룸 나가기 → 연결 해제 → 정리) |
| LiveRelayClient.checkBrowserSupport() | 브라우저 호환성 확인 (정적 메서드) |
자주 묻는 질문
Q: 서버 주소는 어디서 받나요?
관리자에게 문의하세요. 테넌트 ID와 API 키도 함께 받아야 합니다.
Q: React에서 쓸 수 있나요?
네. useEffect에서 connect()하고, useRef로 client를 관리하세요.
import { useEffect, useRef } from 'react';
import { LiveRelayClient } from '@dany-kim/liverelay-sdk';
function VideoCall() {
const clientRef = useRef(null);
useEffect(() => {
const client = new LiveRelayClient({
tenantId: 'my-company',
apiKey: 'sk_abc123',
serverUrl: 'https://rtc-insa.insahr.co.kr'
});
clientRef.current = client;
client.connect().then(() => {
client.joinRoom({ roomId: 'room-1', userName: '김철수' });
});
return () => {
client.leaveRoom();
client.disconnect();
};
}, []);
return <div>화상회의</div>;
}Q: Vue에서 쓸 수 있나요?
네. onMounted에서 연결하세요.
<script setup>
import { onMounted, onUnmounted, ref } from 'vue';
import { LiveRelayClient } from '@dany-kim/liverelay-sdk';
const client = ref(null);
onMounted(async () => {
client.value = new LiveRelayClient({
tenantId: 'my-company',
apiKey: 'sk_abc123',
serverUrl: 'https://rtc-insa.insahr.co.kr'
});
await client.value.connect();
await client.value.joinRoom({ roomId: 'room-1', userName: '김철수' });
});
onUnmounted(() => {
client.value?.leaveRoom();
client.value?.disconnect();
});
</script>Q: 브라우저 호환성은?
Chrome, Firefox, Safari, Edge 최신 버전을 지원합니다. Internet Explorer는 지원하지 않습니다.
LiveRelayClient.checkBrowserSupport()로 런타임에 확인할 수 있습니다:
const support = LiveRelayClient.checkBrowserSupport();
if (!support.supported) {
console.warn('미지원 브라우저:', support.warnings);
}Q: 화이트보드/소모임 같은 확장 기능은 어떻게 쓰나요?
getSocket()으로 소켓 인스턴스에 직접 접근하여 서버의 화이트보드(wb:*) 및 소모임(bo:*) 이벤트를 사용할 수 있습니다.
const socket = client.getSocket();
// 화이트보드 열기 (관리자)
socket.emit('wb:open', { allowParticipantDraw: true }, (res) => {
console.log('화이트보드:', res);
});
// 소모임 생성 (관리자)
socket.emit('bo:create', {
groups: 3,
timerMinutes: 10,
assignMode: 'auto'
}, (res) => {
console.log('소모임:', res);
});Q: 네트워크 끊김 시 자동 재연결되나요?
네. autoReconnect 옵션(기본값: true)이 활성화되어 있으면 네트워크 끊김 시 자동 재연결하고, 재연결 성공 시 이전 룸에 자동 재입장합니다. connection-state-changed 이벤트로 상태를 모니터링할 수 있습니다.
Q: 리소스 정리는 어떻게 하나요?
destroy() 메서드를 호출하면 녹화 중지 → 룸 나가기 → 연결 해제 → 내부 리소스 정리를 순서대로 처리합니다. React의 useEffect cleanup이나 Vue의 onUnmounted에서 호출하세요.
