{"_id":"@dany-kim/liverelay-sdk","_rev":"6-977e19224ac24d6296b16e475d7eeec0","name":"@dany-kim/liverelay-sdk","dist-tags":{"latest":"2.2.5"},"versions":{"2.2.0":{"name":"@dany-kim/liverelay-sdk","version":"2.2.0","keywords":["webrtc","mediasoup","video-conferencing","real-time-communication","streaming","enterprise","sdk","typescript","react","vue","javascript"],"author":{"url":"https://liverelay.com","name":"LiveRelay2 Team","email":"support@liverelay.com"},"license":"MIT","_id":"@dany-kim/liverelay-sdk@2.2.0","maintainers":[{"name":"dany-kim","email":"aladin99@hanmail.net"}],"homepage":"https://liverelay.com/sdk","bugs":{"url":"https://github.com/LiveRelay/LiveRelay2-SDK/issues"},"dist":{"shasum":"303bb254c35dff5f90c79a6dcdf8feb4a76365f3","tarball":"https://registry.npmjs.org/@dany-kim/liverelay-sdk/-/liverelay-sdk-2.2.0.tgz","fileCount":52,"integrity":"sha512-xuFRk77Ys7f0rG0mMN6i/GKC5dPmv9KO20m59pPfpqtw7Oaxcz0n0YIpD/ly8sjcu+X+iO9Eae3N3Wi8iZdmZw==","signatures":[{"sig":"MEUCIQDUAIs24IA70yIrb0ehxzi4ptagimcMyCaiyNavK0odAwIgU+iPZawq4XafsEBBTs1MbMNe1vpl/Xrdopeps4UjDhc=","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":898390},"main":"dist/index.js","types":"dist/index.d.ts","gitHead":"d36810c94c73150797c07c7bf5a06f3cfd66a177","scripts":{"dev":"tsc --watch","lint":"eslint src/**/*.ts","test":"jest","build":"tsc","prepublish":"npm run build"},"_npmUser":{"name":"dany-kim","email":"aladin99@hanmail.net"},"repository":{"url":"git+https://github.com/LiveRelay/LiveRelay2-SDK.git","type":"git","directory":"sdk"},"_npmVersion":"11.5.1","description":"LiveRelay SDK - Enterprise-grade real-time media streaming and communication platform","directories":{},"_nodeVersion":"20.18.1","dependencies":{"eventemitter3":"^5.0.1","mediasoup-client":"^3.7.18","socket.io-client":"^4.8.1"},"_hasShrinkwrap":false,"devDependencies":{"jest":"^29.0.0","eslint":"^8.0.0","ts-jest":"^29.1.0","typescript":"^5.0.0","@types/jest":"^29.0.0","@types/node":"^20.0.0","jest-websocket-mock":"^2.5.0","jest-environment-jsdom":"^29.0.0","@testing-library/jest-dom":"^6.0.0","@typescript-eslint/parser":"^6.0.0","@typescript-eslint/eslint-plugin":"^6.0.0"},"peerDependencies":{"typescript":">=4.5.0"},"_npmOperationalInternal":{"tmp":"tmp/liverelay-sdk_2.2.0_1774393163578_0.14055970890286806","host":"s3://npm-registry-packages-npm-production"}},"2.2.1":{"name":"@dany-kim/liverelay-sdk","version":"2.2.1","keywords":["webrtc","mediasoup","video-conferencing","real-time-communication","streaming","enterprise","sdk","typescript","react","vue","javascript"],"author":{"url":"https://liverelay.com","name":"LiveRelay2 Team","email":"support@liverelay.com"},"license":"MIT","_id":"@dany-kim/liverelay-sdk@2.2.1","maintainers":[{"name":"dany-kim","email":"aladin99@hanmail.net"}],"homepage":"https://liverelay.com/sdk","bugs":{"url":"https://github.com/LiveRelay/LiveRelay2-SDK/issues"},"dist":{"shasum":"f63adbfb62880de013aa449f5149ce821be89929","tarball":"https://registry.npmjs.org/@dany-kim/liverelay-sdk/-/liverelay-sdk-2.2.1.tgz","fileCount":52,"integrity":"sha512-Zpq8FT5qzvWxzg8TBlsD+Cvl+Ax+1aQxVZwPRwtdIzzsSCUVPG8z7QkcPZ961hLF7Him9LNR27Cejuaku5CG/Q==","signatures":[{"sig":"MEQCIENcJiKbvKMdyLiIrJ9vfdK0fmgcXCT3Kqn39jsH5gQaAiBDA4qnOX/h9U7JvltW+B3jSUC9oNwb+tT0R6KCxC82wQ==","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":898029},"main":"dist/index.js","types":"dist/index.d.ts","gitHead":"138354e88b846d1b64796f9b8340ea9cb282b29b","scripts":{"dev":"tsc --watch","lint":"eslint src/**/*.ts","test":"jest","build":"tsc","prepublish":"npm run build"},"_npmUser":{"name":"dany-kim","email":"aladin99@hanmail.net"},"repository":{"url":"git+https://github.com/LiveRelay/LiveRelay2-SDK.git","type":"git","directory":"sdk"},"_npmVersion":"11.5.1","description":"LiveRelay SDK - Enterprise-grade real-time media streaming and communication platform","directories":{},"_nodeVersion":"20.18.1","dependencies":{"eventemitter3":"^5.0.1","mediasoup-client":"^3.7.18","socket.io-client":"^4.8.1"},"_hasShrinkwrap":false,"devDependencies":{"jest":"^29.0.0","eslint":"^8.0.0","ts-jest":"^29.1.0","typescript":"^5.0.0","@types/jest":"^29.0.0","@types/node":"^20.0.0","jest-websocket-mock":"^2.5.0","jest-environment-jsdom":"^29.0.0","@testing-library/jest-dom":"^6.0.0","@typescript-eslint/parser":"^6.0.0","@typescript-eslint/eslint-plugin":"^6.0.0"},"peerDependencies":{"typescript":">=4.5.0"},"_npmOperationalInternal":{"tmp":"tmp/liverelay-sdk_2.2.1_1774398063217_0.36628723886235326","host":"s3://npm-registry-packages-npm-production"}},"2.2.2":{"name":"@dany-kim/liverelay-sdk","version":"2.2.2","keywords":["webrtc","mediasoup","video-conferencing","real-time-communication","streaming","enterprise","sdk","typescript","react","vue","javascript"],"author":{"url":"https://liverelay.com","name":"LiveRelay2 Team","email":"support@liverelay.com"},"license":"MIT","_id":"@dany-kim/liverelay-sdk@2.2.2","maintainers":[{"name":"dany-kim","email":"aladin99@hanmail.net"}],"homepage":"https://liverelay.com/sdk","bugs":{"url":"https://github.com/LiveRelay/LiveRelay2-SDK/issues"},"dist":{"shasum":"5c4daab91b238eb6e7b793ebae0fe43769959a66","tarball":"https://registry.npmjs.org/@dany-kim/liverelay-sdk/-/liverelay-sdk-2.2.2.tgz","fileCount":52,"integrity":"sha512-xuR1lTp4J7g+ylk29ccGf2GWspH9DqP3aw6PVtAEjL0hd5z+6U97Ic2Hr47Z6mGjig9PSDgTzKK0sjfsTKf//A==","signatures":[{"sig":"MEYCIQC41Cuo7wAJUSEE2yVpGwWUHR1xyW3kkkdUAXp0QPhAjwIhAMz+n7dom62TZOypA9ZYxMRLGJg2exZ+PfYwQz/26rHD","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":889678},"main":"dist/index.js","types":"dist/index.d.ts","gitHead":"46e5defce0e79b5c381ca23f37db07915448cf1f","scripts":{"dev":"tsc --watch","lint":"eslint src/**/*.ts","test":"jest","build":"tsc","prepublish":"npm run build"},"_npmUser":{"name":"dany-kim","email":"aladin99@hanmail.net"},"repository":{"url":"git+https://github.com/LiveRelay/LiveRelay2-SDK.git","type":"git","directory":"sdk"},"_npmVersion":"11.5.1","description":"LiveRelay SDK - Enterprise-grade real-time media streaming and communication platform","directories":{},"_nodeVersion":"20.18.1","dependencies":{"eventemitter3":"^5.0.1","mediasoup-client":"^3.7.18","socket.io-client":"^4.8.1"},"_hasShrinkwrap":false,"devDependencies":{"jest":"^29.0.0","eslint":"^8.0.0","ts-jest":"^29.1.0","typescript":"^5.0.0","@types/jest":"^29.0.0","@types/node":"^20.0.0","jest-websocket-mock":"^2.5.0","jest-environment-jsdom":"^29.0.0","@testing-library/jest-dom":"^6.0.0","@typescript-eslint/parser":"^6.0.0","@typescript-eslint/eslint-plugin":"^6.0.0"},"peerDependencies":{"typescript":">=4.5.0"},"_npmOperationalInternal":{"tmp":"tmp/liverelay-sdk_2.2.2_1774425146884_0.9820317473028315","host":"s3://npm-registry-packages-npm-production"}},"2.2.3":{"name":"@dany-kim/liverelay-sdk","version":"2.2.3","keywords":["webrtc","mediasoup","video-conferencing","real-time-communication","streaming","enterprise","sdk","typescript","react","vue","javascript"],"author":{"url":"https://liverelay.com","name":"LiveRelay2 Team","email":"support@liverelay.com"},"license":"MIT","_id":"@dany-kim/liverelay-sdk@2.2.3","maintainers":[{"name":"dany-kim","email":"aladin99@hanmail.net"}],"homepage":"https://liverelay.com/sdk","bugs":{"url":"https://github.com/LiveRelay/LiveRelay2-SDK/issues"},"dist":{"shasum":"8ecbfb5e6a813e8ca5330c1d110bec18e76c9dd1","tarball":"https://registry.npmjs.org/@dany-kim/liverelay-sdk/-/liverelay-sdk-2.2.3.tgz","fileCount":52,"integrity":"sha512-iEoSx5T3vVlRF2yjIOFv/C3S0KSbn8TZxtAkzFFGwHN/Z9IQjbW56QQ2RvADuf4siyAJBHKd1tByYjrvaMHv1A==","signatures":[{"sig":"MEUCIBzSVYDsEzSdeHfEpCG+WRTswLRO+b0pwokqIzIiL/2LAiEAqSgxv+zW9S7M7Jwts6Eh1NoOzU/H1SU1IXc6tw20aqo=","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":890047},"main":"dist/index.js","types":"dist/index.d.ts","gitHead":"46e5defce0e79b5c381ca23f37db07915448cf1f","scripts":{"dev":"tsc --watch","lint":"eslint src/**/*.ts","test":"jest","build":"tsc","prepublish":"npm run build"},"_npmUser":{"name":"dany-kim","email":"aladin99@hanmail.net"},"repository":{"url":"git+https://github.com/LiveRelay/LiveRelay2-SDK.git","type":"git","directory":"sdk"},"_npmVersion":"11.5.1","description":"LiveRelay SDK - Enterprise-grade real-time media streaming and communication platform","directories":{},"_nodeVersion":"20.18.1","dependencies":{"eventemitter3":"^5.0.1","mediasoup-client":"^3.7.18","socket.io-client":"^4.8.1"},"_hasShrinkwrap":false,"devDependencies":{"jest":"^29.0.0","eslint":"^8.0.0","ts-jest":"^29.1.0","typescript":"^5.0.0","@types/jest":"^29.0.0","@types/node":"^20.0.0","jest-websocket-mock":"^2.5.0","jest-environment-jsdom":"^29.0.0","@testing-library/jest-dom":"^6.0.0","@typescript-eslint/parser":"^6.0.0","@typescript-eslint/eslint-plugin":"^6.0.0"},"peerDependencies":{"typescript":">=4.5.0"},"_npmOperationalInternal":{"tmp":"tmp/liverelay-sdk_2.2.3_1774488164991_0.987149466195758","host":"s3://npm-registry-packages-npm-production"}},"2.2.4":{"name":"@dany-kim/liverelay-sdk","version":"2.2.4","keywords":["webrtc","mediasoup","video-conferencing","real-time-communication","streaming","enterprise","sdk","typescript","react","vue","javascript"],"author":{"url":"https://liverelay.com","name":"LiveRelay2 Team","email":"support@liverelay.com"},"license":"MIT","_id":"@dany-kim/liverelay-sdk@2.2.4","maintainers":[{"name":"dany-kim","email":"aladin99@hanmail.net"}],"homepage":"https://liverelay.com/sdk","bugs":{"url":"https://github.com/LiveRelay/LiveRelay2-SDK/issues"},"dist":{"shasum":"88b2f922c5db9f3ae4974c64174c87d425cf4288","tarball":"https://registry.npmjs.org/@dany-kim/liverelay-sdk/-/liverelay-sdk-2.2.4.tgz","fileCount":52,"integrity":"sha512-OVNfYI1zyzZqZVTGefpfpVhPvzoc2RMvnyAH9U9pb/8o79XehoCrCTOSov4XF7H+RdlF/KHLFq/hpfNPAslfKg==","signatures":[{"sig":"MEQCIC2TUv63qEkwsn6DC8HmAxReezBWRQHuSiewQ9CeWx+qAiBVH+YQdfav2d3k7XktttxWW6IrM25uJorhyo9ReKbYqA==","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":890487},"main":"dist/index.js","types":"dist/index.d.ts","gitHead":"46e5defce0e79b5c381ca23f37db07915448cf1f","scripts":{"dev":"tsc --watch","lint":"eslint src/**/*.ts","test":"jest","build":"tsc","prepublish":"npm run build"},"_npmUser":{"name":"dany-kim","email":"aladin99@hanmail.net"},"repository":{"url":"git+https://github.com/LiveRelay/LiveRelay2-SDK.git","type":"git","directory":"sdk"},"_npmVersion":"11.5.1","description":"LiveRelay SDK - Enterprise-grade real-time media streaming and communication platform","directories":{},"_nodeVersion":"20.18.1","dependencies":{"eventemitter3":"^5.0.1","mediasoup-client":"^3.7.18","socket.io-client":"^4.8.1"},"_hasShrinkwrap":false,"devDependencies":{"jest":"^29.0.0","eslint":"^8.0.0","ts-jest":"^29.1.0","typescript":"^5.0.0","@types/jest":"^29.0.0","@types/node":"^20.0.0","jest-websocket-mock":"^2.5.0","jest-environment-jsdom":"^29.0.0","@testing-library/jest-dom":"^6.0.0","@typescript-eslint/parser":"^6.0.0","@typescript-eslint/eslint-plugin":"^6.0.0"},"peerDependencies":{"typescript":">=4.5.0"},"_npmOperationalInternal":{"tmp":"tmp/liverelay-sdk_2.2.4_1774489175257_0.4380863722894619","host":"s3://npm-registry-packages-npm-production"}},"2.2.5":{"name":"@dany-kim/liverelay-sdk","version":"2.2.5","description":"LiveRelay SDK - Enterprise-grade real-time media streaming and communication platform","main":"dist/index.js","types":"dist/index.d.ts","scripts":{"build":"tsc","dev":"tsc --watch","test":"jest","lint":"eslint src/**/*.ts","prepublish":"npm run build"},"keywords":["webrtc","mediasoup","video-conferencing","real-time-communication","streaming","enterprise","sdk","typescript","react","vue","javascript"],"author":{"name":"LiveRelay2 Team","email":"support@liverelay.com","url":"https://liverelay.com"},"license":"MIT","homepage":"https://liverelay.com/sdk","dependencies":{"socket.io-client":"^4.8.1","mediasoup-client":"^3.7.18","eventemitter3":"^5.0.1"},"devDependencies":{"@types/node":"^20.0.0","typescript":"^5.0.0","jest":"^29.0.0","@types/jest":"^29.0.0","ts-jest":"^29.1.0","jest-websocket-mock":"^2.5.0","jest-environment-jsdom":"^29.0.0","@testing-library/jest-dom":"^6.0.0","eslint":"^8.0.0","@typescript-eslint/eslint-plugin":"^6.0.0","@typescript-eslint/parser":"^6.0.0"},"repository":{"type":"git","url":"git+https://github.com/LiveRelay/LiveRelay2-SDK.git","directory":"sdk"},"bugs":{"url":"https://github.com/LiveRelay/LiveRelay2-SDK/issues"},"peerDependencies":{"typescript":">=4.5.0"},"_id":"@dany-kim/liverelay-sdk@2.2.5","gitHead":"06d9a6ae7e038499fd968715c4a92b4130b75e6f","_nodeVersion":"20.18.1","_npmVersion":"11.5.1","dist":{"integrity":"sha512-aswioQdnd1CDZL3OdxxEqiyEf9os/kO6dRQ55oDRl8glKcbGXCunSzlEBFov9LBFrSgpwShhUlvpBNY+TYZy8w==","shasum":"d31f5ae436665cafb792ac1a4fa3106a159d9c89","tarball":"https://registry.npmjs.org/@dany-kim/liverelay-sdk/-/liverelay-sdk-2.2.5.tgz","fileCount":52,"unpackedSize":894895,"signatures":[{"keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U","sig":"MEYCIQDpNFdaWjrasa9NlbFiO8LpKAr6gyCAvkCoeUvuSPAgnwIhAJky5SMzcTdk1wma5dVUFlrf8Xq0IdX17qp3Fypqdi31"}]},"_npmUser":{"name":"dany-kim","email":"aladin99@hanmail.net"},"directories":{},"maintainers":[{"name":"dany-kim","email":"aladin99@hanmail.net"}],"_npmOperationalInternal":{"host":"s3://npm-registry-packages-npm-production","tmp":"tmp/liverelay-sdk_2.2.5_1775025954229_0.542891362261924"},"_hasShrinkwrap":false}},"time":{"created":"2026-03-24T22:59:23.481Z","modified":"2026-04-01T06:45:54.626Z","2.2.0":"2026-03-24T22:59:23.787Z","2.2.1":"2026-03-25T00:21:03.437Z","2.2.2":"2026-03-25T07:52:27.065Z","2.2.3":"2026-03-26T01:22:45.199Z","2.2.4":"2026-03-26T01:39:35.515Z","2.2.5":"2026-04-01T06:45:54.523Z"},"bugs":{"url":"https://github.com/LiveRelay/LiveRelay2-SDK/issues"},"author":{"name":"LiveRelay2 Team","email":"support@liverelay.com","url":"https://liverelay.com"},"license":"MIT","homepage":"https://liverelay.com/sdk","keywords":["webrtc","mediasoup","video-conferencing","real-time-communication","streaming","enterprise","sdk","typescript","react","vue","javascript"],"repository":{"type":"git","url":"git+https://github.com/LiveRelay/LiveRelay2-SDK.git","directory":"sdk"},"description":"LiveRelay SDK - Enterprise-grade real-time media streaming and communication platform","maintainers":[{"name":"dany-kim","email":"aladin99@hanmail.net"}],"readme":"# LiveRelay SDK 사용 가이드\n\n화상회의 기능을 내 웹사이트에 추가하는 SDK입니다.\n\n## 설치\n\n```bash\nnpm install @dany-kim/liverelay-sdk\n```\n\n---\n\n## 라이브 데모\n\n| 데모 | URL | 설명 |\n|------|-----|------|\n| 화상회의 (Zoom 스타일) | `{서버주소}/sdk-demo` | 방 생성/참여, 양방향 영상·음성·채팅 |\n| 멀티뷰 (관제 모드) | `{서버주소}/sdk-demo-multiview` | 수신 전용 모니터링, 그리드 뷰, 스폿라이트 |\n\n> 예: `https://rtc-insa.insahr.co.kr/sdk-demo`\n\n**멀티뷰 데모 사용법:**\n1. 멀티뷰에서 \"방 만들기\"로 방을 생성하면 공유 링크가 표시됩니다.\n2. 참가자들이 **화상회의 데모(`/sdk-demo`)**에서 해당 코드로 입장합니다.\n3. 참가자가 카메라를 켜면 멀티뷰 화면에 자동으로 영상이 나타납니다.\n\n> 멀티뷰는 수신 전용이므로, 참가자가 최소 1명 이상 접속해야 영상을 확인할 수 있습니다.\n\n---\n\n## 1단계: 연결하기\n\n```javascript\nimport { LiveRelayClient } from '@dany-kim/liverelay-sdk';\n\n// SDK 만들기\nconst client = new LiveRelayClient({\n  tenantId: 'my-company',           // 우리 회사 ID\n  apiKey: 'sk_my_company_abc123',   // 발급받은 API 키\n  serverUrl: 'https://rtc-insa.insahr.co.kr'  // 서버 주소\n});\n\n// 서버에 연결\nawait client.connect();\nconsole.log('서버 연결 성공!');\n```\n\n---\n\n## 2단계: 방 입장하기\n\n```javascript\nconst room = await client.joinRoom({\n  roomId: 'meeting-123',    // 방 번호 (아무 문자열)\n  userName: '김철수',        // 내 이름\n  userRole: 'participant'   // 역할: 'admin' 또는 'participant'\n});\n\nconsole.log('방 입장 성공!', room.id);\n```\n\n---\n\n## 3단계: 카메라 켜기\n\n```javascript\n// 카메라 켜기\nawait client.enableCamera();\nconsole.log('카메라 켜짐!');\n\n// 카메라 끄기\nawait client.disableCamera();\n```\n\n---\n\n## 4단계: 마이크 켜기\n\n```javascript\n// 마이크 켜기\nawait client.enableMicrophone();\n\n// 마이크 끄기\nawait client.disableMicrophone();\n```\n\n---\n\n## 5단계: 화면 공유\n\n```javascript\n// 내 화면 공유 시작\nawait client.shareScreen();\n\n// 화면 공유 중지\nawait client.stopScreenShare();\n```\n\n---\n\n## 6단계: 다른 사람 영상 보기\n\n다른 사람이 카메라를 켜면 자동으로 알림이 옵니다.\n\n```javascript\n// 누군가 영상을 보내기 시작하면\nclient.on('stream-added', ({ stream }) => {\n  // stream.stream이 MediaStream 객체\n  const videoElement = document.createElement('video');\n  videoElement.srcObject = stream.stream;\n  videoElement.autoplay = true;\n  videoElement.playsInline = true;\n  document.getElementById('videos').appendChild(videoElement);\n});\n\n// 영상이 끊기면\nclient.on('stream-removed', ({ streamId }) => {\n  console.log('영상 종료:', streamId);\n});\n```\n\n---\n\n## 7단계: 채팅 보내기\n\n```javascript\n// 전체 채팅\nawait client.sendPublicMessage('안녕하세요!');\n\n// 특정 사람에게 귓속말\nawait client.sendPrivateMessage('비밀 메시지', '상대방ID');\n\n// 채팅 받기\nclient.on('chat-message-received', ({ message }) => {\n  console.log(`${message.sender}: ${message.message}`);\n});\n```\n\n---\n\n## 8단계: 참가자 관리\n\n```javascript\n// 현재 참가자 목록 보기\nconst participants = client.getParticipants();\nconsole.log('참가자:', participants);\n\n// 누가 들어오면\nclient.on('participant-joined', ({ participant }) => {\n  console.log(`${participant.name}님이 입장했습니다`);\n});\n\n// 누가 나가면\nclient.on('participant-left', ({ participantId }) => {\n  console.log(`${participantId}님이 퇴장했습니다`);\n});\n```\n\n---\n\n## 9단계: 방 나가기\n\n```javascript\nawait client.leaveRoom();\nclient.disconnect();\nconsole.log('방을 나왔습니다');\n```\n\n---\n\n## 전체 예제 (복사해서 바로 사용)\n\n### HTML 파일 하나로 화상회의 만들기\n\n```html\n<!DOCTYPE html>\n<html lang=\"ko\">\n<head>\n  <meta charset=\"UTF-8\">\n  <title>내 화상회의</title>\n  <style>\n    body { font-family: sans-serif; max-width: 800px; margin: 40px auto; }\n    #videos { display: flex; flex-wrap: wrap; gap: 10px; margin: 20px 0; }\n    #videos video { width: 300px; border-radius: 8px; background: #000; }\n    button { padding: 10px 20px; margin: 5px; border-radius: 6px; border: none; cursor: pointer; font-size: 14px; }\n    .btn-green { background: #22c55e; color: white; }\n    .btn-red { background: #ef4444; color: white; }\n    .btn-blue { background: #3b82f6; color: white; }\n    #chat { border: 1px solid #ddd; border-radius: 8px; padding: 10px; height: 200px; overflow-y: auto; margin: 10px 0; }\n    #chat p { margin: 4px 0; }\n    input { padding: 8px 12px; border: 1px solid #ddd; border-radius: 6px; }\n  </style>\n</head>\n<body>\n  <h1>내 화상회의</h1>\n\n  <!-- 1. 입장 폼 -->\n  <div id=\"login\">\n    <input id=\"userName\" placeholder=\"이름 입력\" value=\"테스트유저\" />\n    <input id=\"roomId\" placeholder=\"방 번호\" value=\"test-room\" />\n    <button class=\"btn-blue\" onclick=\"joinMeeting()\">입장하기</button>\n  </div>\n\n  <!-- 2. 회의 화면 (처음엔 숨김) -->\n  <div id=\"meeting\" style=\"display:none\">\n    <p id=\"status\">연결 중...</p>\n\n    <div>\n      <button class=\"btn-green\" onclick=\"toggleCamera()\">카메라 ON/OFF</button>\n      <button class=\"btn-green\" onclick=\"toggleMic()\">마이크 ON/OFF</button>\n      <button class=\"btn-blue\" onclick=\"toggleScreen()\">화면공유</button>\n      <button class=\"btn-red\" onclick=\"leaveMeeting()\">나가기</button>\n    </div>\n\n    <div id=\"videos\">\n      <video id=\"localVideo\" autoplay muted playsinline></video>\n    </div>\n\n    <div id=\"chat\"></div>\n    <div>\n      <input id=\"chatInput\" placeholder=\"메시지 입력...\" onkeypress=\"if(event.key==='Enter') sendChat()\" />\n      <button class=\"btn-blue\" onclick=\"sendChat()\">전송</button>\n    </div>\n  </div>\n\n  <script type=\"module\">\n    import { LiveRelayClient } from '@dany-kim/liverelay-sdk';\n\n    // ===== 설정 (본인 서버 정보로 변경) =====\n    const SERVER_URL = 'https://rtc-insa.insahr.co.kr';\n    const TENANT_ID = 'my-company';\n    const API_KEY = 'sk_my_company_abc123';\n\n    let client = null;\n    let cameraOn = false;\n    let micOn = false;\n    let screenOn = false;\n\n    // 입장하기\n    window.joinMeeting = async function() {\n      const userName = document.getElementById('userName').value;\n      const roomId = document.getElementById('roomId').value;\n\n      if (!userName || !roomId) {\n        alert('이름과 방 번호를 입력하세요');\n        return;\n      }\n\n      try {\n        // 1. SDK 생성 + 연결\n        client = new LiveRelayClient({\n          tenantId: TENANT_ID,\n          apiKey: API_KEY,\n          serverUrl: SERVER_URL\n        });\n\n        await client.connect();\n\n        // 2. 방 입장\n        await client.joinRoom({ roomId, userName, userRole: 'participant' });\n\n        // 3. 이벤트 등록\n        setupEvents();\n\n        // 4. 화면 전환\n        document.getElementById('login').style.display = 'none';\n        document.getElementById('meeting').style.display = 'block';\n        document.getElementById('status').textContent = `${roomId} 방에 입장했습니다 (${userName})`;\n\n      } catch (error) {\n        alert('입장 실패: ' + error.message);\n      }\n    };\n\n    // 이벤트 등록\n    function setupEvents() {\n      // 다른 사람 영상이 도착하면\n      client.on('stream-added', ({ stream }) => {\n        const video = document.createElement('video');\n        video.id = 'remote-' + stream.id;\n        video.srcObject = stream.stream;\n        video.autoplay = true;\n        video.playsInline = true;\n        document.getElementById('videos').appendChild(video);\n      });\n\n      // 영상이 끊기면\n      client.on('stream-removed', ({ streamId }) => {\n        const video = document.getElementById('remote-' + streamId);\n        if (video) video.remove();\n      });\n\n      // 채팅 수신\n      client.on('chat-message-received', ({ message }) => {\n        const chat = document.getElementById('chat');\n        const p = document.createElement('p');\n        const sender = message.senderName || message.sender || '알수없음';\n        const text = message.content || message.message || '';\n        p.textContent = `${sender}: ${text}`;\n        chat.appendChild(p);\n        chat.scrollTop = chat.scrollHeight;\n      });\n\n      // 참가자 입장/퇴장\n      client.on('participant-joined', ({ participant }) => {\n        addChatLine(`[${participant.name}님이 입장했습니다]`);\n      });\n\n      client.on('participant-left', ({ participantId }) => {\n        addChatLine(`[${participantId}님이 퇴장했습니다]`);\n      });\n    }\n\n    function addChatLine(text) {\n      const chat = document.getElementById('chat');\n      const p = document.createElement('p');\n      p.style.color = '#888';\n      p.textContent = text;\n      chat.appendChild(p);\n    }\n\n    // 카메라 켜기/끄기\n    window.toggleCamera = async function() {\n      if (!client) return;\n      if (cameraOn) {\n        await client.disableCamera();\n        document.getElementById('localVideo').srcObject = null;\n      } else {\n        await client.enableCamera();\n        // 로컬 비디오 표시\n        const stream = client.getLocalStream('camera');\n        if (stream) {\n          document.getElementById('localVideo').srcObject = stream;\n        }\n      }\n      cameraOn = !cameraOn;\n    };\n\n    // 마이크 켜기/끄기\n    window.toggleMic = async function() {\n      if (!client) return;\n      if (micOn) {\n        await client.disableMicrophone();\n      } else {\n        await client.enableMicrophone();\n      }\n      micOn = !micOn;\n    };\n\n    // 화면공유\n    window.toggleScreen = async function() {\n      if (!client) return;\n      if (screenOn) {\n        await client.stopScreenShare();\n      } else {\n        await client.shareScreen();\n      }\n      screenOn = !screenOn;\n    };\n\n    // 채팅 전송\n    window.sendChat = async function() {\n      const input = document.getElementById('chatInput');\n      const text = input.value.trim();\n      if (!text || !client) return;\n\n      await client.sendPublicMessage(text);\n      addChatLine(`나: ${text}`);\n      input.value = '';\n    };\n\n    // 나가기\n    window.leaveMeeting = async function() {\n      if (client) {\n        await client.leaveRoom();\n        client.disconnect();\n        client = null;\n      }\n      document.getElementById('meeting').style.display = 'none';\n      document.getElementById('login').style.display = 'block';\n    };\n  </script>\n</body>\n</html>\n```\n\n---\n\n## 이벤트 목록 (전체)\n\n| 이벤트 | 언제 발생하나 | 받는 데이터 |\n|--------|-------------|-------------|\n| `connection-state-changed` | 연결 상태가 바뀔 때 | `{ state, reason }` |\n| `room-joined` | 방에 들어갔을 때 | `{ room }` |\n| `room-left` | 방에서 나왔을 때 | `{ roomId, reason }` |\n| `participant-joined` | 다른 사람이 들어왔을 때 | `{ participant }` |\n| `participant-left` | 다른 사람이 나갔을 때 | `{ participantId }` |\n| `stream-added` | 영상/오디오가 도착했을 때 | `{ stream }` |\n| `stream-removed` | 영상/오디오가 끊겼을 때 | `{ streamId }` |\n| `chat-message-received` | 채팅 메시지를 받았을 때 | `{ message }` |\n| `chat-permissions-changed` | 채팅 권한이 바뀔 때 | `{ permissions }` |\n| `local-media-changed` | 내 카메라/마이크 상태 변경 | `{ type, enabled }` |\n| `error` | 오류 발생 | `{ error }` |\n\n---\n\n## 주요 메서드 목록\n\n### 연결\n| 메서드 | 설명 |\n|--------|------|\n| `connect()` | 서버에 연결 |\n| `disconnect()` | 서버 연결 끊기 |\n| `joinRoom(config)` | 방 입장 |\n| `leaveRoom()` | 방 나가기 |\n| `testConnection()` | 연결 품질 테스트 (지연시간/품질 반환) |\n| `getConnectionState()` | 연결 상태 조회 (`connected`, `disconnected`, `reconnecting` 등) |\n\n### 미디어\n| 메서드 | 설명 |\n|--------|------|\n| `enableCamera(constraints?)` | 카메라 켜기 (선택적 제약 조건) |\n| `disableCamera()` | 카메라 끄기 |\n| `enableMicrophone(constraints?)` | 마이크 켜기 (선택적 제약 조건) |\n| `disableMicrophone()` | 마이크 끄기 |\n| `shareScreen(options?)` | 화면공유 시작 |\n| `stopScreenShare()` | 화면공유 중지 |\n| `getLocalStream(type)` | 내 스트림 가져오기 (`'camera'`, `'microphone'`, `'screen'`) |\n| `getRemoteStreams()` | 원격 참가자 스트림 목록 조회 |\n| `subscribeToParticipant(id)` | 특정 참가자 스트림 구독 |\n| `unsubscribeFromParticipant(id)` | 특정 참가자 스트림 구독 해제 |\n| `startRecording(options?)` | 클라이언트 사이드 녹화 시작 |\n| `stopRecording(recordingId)` | 클라이언트 사이드 녹화 중지 |\n\n### 채팅\n| 메서드 | 설명 |\n|--------|------|\n| `sendPublicMessage(text)` | 전체 채팅 |\n| `sendPrivateMessage(text, targetId)` | 귓속말 |\n| `sendMessage(content, type, targetId?)` | 통합 메시지 전송 |\n| `getChatHistory()` | 채팅 기록 가져오기 |\n| `searchMessages(query)` | 채팅 메시지 검색 |\n\n### 참가자\n| 메서드 | 설명 |\n|--------|------|\n| `getParticipants()` | 참가자 목록 |\n| `getLocalParticipant()` | 내 정보 |\n| `getParticipant(id)` | 특정 참가자 정보 조회 |\n| `getConnectedParticipants()` | 연결된 참가자만 |\n| `getAdmins()` | 관리자 목록 조회 |\n\n### 관리자 전용\n| 메서드 | 설명 |\n|--------|------|\n| `removeParticipant(id, reason)` | 참가자 강퇴 |\n| `changeParticipantRole(id, role)` | 역할 변경 |\n\n### 상태/통계\n| 메서드 | 설명 |\n|--------|------|\n| `getCurrentRoom()` | 현재 룸 정보 조회 |\n| `getConnectionStats()` | 연결 통계 (대역폭, 지연 등) |\n| `getMediaState()` | 미디어 상태 (카메라/마이크/화면공유 ON/OFF) |\n| `getParticipantStats()` | 참가자 통계 |\n| `getSDKStats()` | SDK 전체 통계 (연결, 미디어, 성능, 세션) |\n| `getSocket()` | 소켓 인스턴스 직접 접근 (화이트보드/소모임 등 확장 기능용) |\n\n### 유틸리티\n| 메서드 | 설명 |\n|--------|------|\n| `getVersion()` | SDK 버전 조회 |\n| `getInstanceId()` | 인스턴스 ID 조회 |\n| `getConfig()` | 설정 정보 조회 |\n| `destroy()` | 리소스 정리 (녹화 중지 → 룸 나가기 → 연결 해제 → 정리) |\n| `LiveRelayClient.checkBrowserSupport()` | 브라우저 호환성 확인 (정적 메서드) |\n\n---\n\n## 자주 묻는 질문\n\n### Q: 서버 주소는 어디서 받나요?\n관리자에게 문의하세요. 테넌트 ID와 API 키도 함께 받아야 합니다.\n\n### Q: React에서 쓸 수 있나요?\n네. `useEffect`에서 `connect()`하고, `useRef`로 client를 관리하세요.\n\n```jsx\nimport { useEffect, useRef } from 'react';\nimport { LiveRelayClient } from '@dany-kim/liverelay-sdk';\n\nfunction VideoCall() {\n  const clientRef = useRef(null);\n\n  useEffect(() => {\n    const client = new LiveRelayClient({\n      tenantId: 'my-company',\n      apiKey: 'sk_abc123',\n      serverUrl: 'https://rtc-insa.insahr.co.kr'\n    });\n    clientRef.current = client;\n\n    client.connect().then(() => {\n      client.joinRoom({ roomId: 'room-1', userName: '김철수' });\n    });\n\n    return () => {\n      client.leaveRoom();\n      client.disconnect();\n    };\n  }, []);\n\n  return <div>화상회의</div>;\n}\n```\n\n### Q: Vue에서 쓸 수 있나요?\n네. `onMounted`에서 연결하세요.\n\n```vue\n<script setup>\nimport { onMounted, onUnmounted, ref } from 'vue';\nimport { LiveRelayClient } from '@dany-kim/liverelay-sdk';\n\nconst client = ref(null);\n\nonMounted(async () => {\n  client.value = new LiveRelayClient({\n    tenantId: 'my-company',\n    apiKey: 'sk_abc123',\n    serverUrl: 'https://rtc-insa.insahr.co.kr'\n  });\n  await client.value.connect();\n  await client.value.joinRoom({ roomId: 'room-1', userName: '김철수' });\n});\n\nonUnmounted(() => {\n  client.value?.leaveRoom();\n  client.value?.disconnect();\n});\n</script>\n```\n\n### Q: 브라우저 호환성은?\nChrome, Firefox, Safari, Edge 최신 버전을 지원합니다. Internet Explorer는 지원하지 않습니다.\n\n`LiveRelayClient.checkBrowserSupport()`로 런타임에 확인할 수 있습니다:\n\n```javascript\nconst support = LiveRelayClient.checkBrowserSupport();\nif (!support.supported) {\n  console.warn('미지원 브라우저:', support.warnings);\n}\n```\n\n### Q: 화이트보드/소모임 같은 확장 기능은 어떻게 쓰나요?\n`getSocket()`으로 소켓 인스턴스에 직접 접근하여 서버의 화이트보드(`wb:*`) 및 소모임(`bo:*`) 이벤트를 사용할 수 있습니다.\n\n```javascript\nconst socket = client.getSocket();\n\n// 화이트보드 열기 (관리자)\nsocket.emit('wb:open', { allowParticipantDraw: true }, (res) => {\n  console.log('화이트보드:', res);\n});\n\n// 소모임 생성 (관리자)\nsocket.emit('bo:create', {\n  groups: 3,\n  timerMinutes: 10,\n  assignMode: 'auto'\n}, (res) => {\n  console.log('소모임:', res);\n});\n```\n\n### Q: 네트워크 끊김 시 자동 재연결되나요?\n네. `autoReconnect` 옵션(기본값: true)이 활성화되어 있으면 네트워크 끊김 시 자동 재연결하고, 재연결 성공 시 이전 룸에 자동 재입장합니다. `connection-state-changed` 이벤트로 상태를 모니터링할 수 있습니다.\n\n### Q: 리소스 정리는 어떻게 하나요?\n`destroy()` 메서드를 호출하면 녹화 중지 → 룸 나가기 → 연결 해제 → 내부 리소스 정리를 순서대로 처리합니다. React의 `useEffect` cleanup이나 Vue의 `onUnmounted`에서 호출하세요.\n","readmeFilename":"README.md"}