npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@dany-kim/liverelay-sdk

v2.2.5

Published

LiveRelay SDK - Enterprise-grade real-time media streaming and communication platform

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

멀티뷰 데모 사용법:

  1. 멀티뷰에서 "방 만들기"로 방을 생성하면 공유 링크가 표시됩니다.
  2. 참가자들이 **화상회의 데모(/sdk-demo)**에서 해당 코드로 입장합니다.
  3. 참가자가 카메라를 켜면 멀티뷰 화면에 자동으로 영상이 나타납니다.

멀티뷰는 수신 전용이므로, 참가자가 최소 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에서 호출하세요.