# StreamDock Plugin SDK 공식 문서 백업 > **출처**: https://sdk.key123.vip/ > **GitHub**: https://github.com/MiraboxSpace/StreamDock-Plugin-SDK > **백업일**: 2025-01-27 --- ## 📋 목차 1. [SDK 개요](#sdk-개요) 2. [⚠️ 중요: 자주 발생하는 오류 및 해결법](#중요-자주-발생하는-오류-및-해결법) 3. [지원 언어 및 SDK](#지원-언어-및-sdk) 4. [플러그인이 수신하는 이벤트](#플러그인이-수신하는-이벤트) 5. [플러그인이 전송하는 이벤트](#플러그인이-전송하는-이벤트) 6. [메시지 구조](#메시지-구조) 7. [버전 요구사항](#버전-요구사항) --- ## SDK 개요 ### Stream Dock란? Stream Dock는 **여러 개의 전용 프로그래밍 가능한 버튼을 제공하는 USB 주변기기**입니다. **하드웨어 모델:** - 293 - 293s - 293N3 - 293N4 ### 에코시스템 구성 1. **하드웨어 장치**: 물리적인 StreamDock 버튼 장치 2. **데스크톱 소프트웨어**: 구성 및 관리 소프트웨어 3. **플러그인 시스템**: 확장 가능한 플러그인 아키텍처 ### SDK 설계 원칙 1. **강력함 (Powerful)**: 키 이벤트 트리거를 통해 코드 실행 2. **단순함 (Simple)**: JSON 기반 통신 프로토콜 사용 3. **언어 독립적 (Language-Agnostic)**: WebSocket을 지원하는 모든 언어 사용 가능 (JavaScript 권장) 4. **안전함 (Secure)**: 각 플러그인을 별도 프로세스로 격리 5. **크로스 플랫폼 (Cross-Platform)**: macOS와 Windows 호환 ### 주요 기능 - 프로그래밍 가능한 버튼에 사용자 정의 액션 할당 - 아이콘 및 레이블로 버튼 외형 커스터마이징 - Property Inspector 인터페이스를 통한 액션 구성 - 서드파티 플러그인 및 마켓플레이스 통합 ### 기술 기반 **WebSocket 통신**을 사용하여 플러그인-호스트 간 상호작용을 수행합니다. --- ## ⚠️ 중요: 자주 발생하는 오류 및 해결법 ### 🐛 문제: keyUp 이벤트에서 action UUID를 전달하지 않아 잘못된 액션이 실행됨 **증상**: - 올바른 버튼을 클릭했는데 다른 액션이 실행됨 - 설정(Settings)이 비어있어서 기본값으로 폴백됨 - `didReceiveSettings`에서 받은 설정에 `actionType` 같은 커스텀 필드가 없음 **원인**: ```javascript case 'keyUp': handleButtonClick(jsonObj.context); // ❌ action UUID를 전달하지 않음! break; ``` StreamDock SDK는 버튼의 설정을 **영구 저장**하지만, 설정이 비어있거나 초기화되지 않은 경우: 1. `willAppear` 이벤트에서 설정을 초기화해도 `setSettings`을 호출하지 않으면 저장되지 않음 2. `keyUp` 이벤트에서 `getCurrentSettings(context)`로 가져오면 빈 객체 반환 3. 기본값으로 폴백되어 의도하지 않은 액션 실행 **해결 방법**: #### 방법 1: keyUp에서 action UUID 전달 (권장) ```javascript case 'keyUp': console.log('🔘 버튼 클릭됨!'); console.log('🔍 Action UUID:', jsonObj.action); handleButtonClick(jsonObj.context, jsonObj.action); // ✅ action UUID 전달 break; // 핸들러에서 action UUID로 actionType 결정 function handleButtonClick(context, actionUUID) { const settings = getCurrentSettings(context); let actionType = settings.actionType; // actionType이 없으면 action UUID로 판단 if (!actionType && actionUUID) { if (actionUUID === 'com.example.myaction') { actionType = 'myaction'; } else if (actionUUID === 'com.example.otheraction') { actionType = 'otheraction'; } else { actionType = 'default'; } // 설정에 저장 settings.actionType = actionType; setCurrentSettings(context, settings); } // actionType에 따라 처리 switch (actionType) { case 'myaction': handleMyAction(); break; // ... } } ``` #### 방법 2: willAppear에서 setSettings 호출 ```javascript case 'willAppear': let settings = jsonObj.payload?.settings || {}; // action UUID로 actionType 설정 if (jsonObj.action === 'com.example.myaction') { settings.actionType = 'myaction'; } buttonContexts.set(jsonObj.context, settings); // ✅ StreamDock SDK에 영구 저장 if (websocket) { const setSettingsMessage = { event: 'setSettings', context: jsonObj.context, payload: settings }; websocket.send(JSON.stringify(setSettingsMessage)); } break; ``` #### 방법 3: didReceiveSettings에서 보정 ```javascript case 'didReceiveSettings': if (jsonObj.payload && jsonObj.context) { const newSettings = jsonObj.payload.settings || {}; // actionType이 없으면 action UUID로 보정 if (!newSettings.actionType && jsonObj.action) { if (jsonObj.action === 'com.example.myaction') { newSettings.actionType = 'myaction'; // StreamDock SDK에 저장 if (websocket) { const setSettingsMessage = { event: 'setSettings', context: jsonObj.context, payload: newSettings }; websocket.send(JSON.stringify(setSettingsMessage)); } } } buttonContexts.set(jsonObj.context, newSettings); } break; ``` **핵심 교훈**: 1. **keyUp 이벤트에는 action UUID가 포함되어 있습니다** - 이를 활용하세요! 2. **Settings는 자동으로 저장되지 않습니다** - `setSettings` 이벤트를 명시적으로 보내야 합니다 3. **여러 액션 타입을 처리할 때는 action UUID를 직접 확인**하는 것이 가장 안전합니다 **실제 발생 사례**: - OptiTrack Marker Toggle 버튼을 클릭했는데 Camera Switch 메시지가 전송됨 - Settings가 `{}`로 비어있어서 기본값인 `camera`로 폴백 - action UUID (`com.mirabox.streamingle.optitrack_marker_toggle`)를 확인하지 않아서 발생 **디버깅 팁**: ```javascript case 'keyUp': console.log('============================================'); console.log('버튼 클릭!'); console.log('Context:', jsonObj.context); console.log('Action UUID:', jsonObj.action); // ⭐ 반드시 로그로 확인 console.log('Settings:', getCurrentSettings(jsonObj.context)); console.log('============================================'); handleButtonClick(jsonObj.context, jsonObj.action); break; ``` --- ## 지원 언어 및 SDK StreamDock Plugin SDK는 다양한 프로그래밍 언어를 지원합니다: ### 1. JavaScript SDK (SDJavaScriptSDK) - 순수 JavaScript 구현 - 브라우저 기반 플러그인 - 가장 단순한 구조 ### 2. Node.js SDK V2 (SDNodeJsSDKV2) ⭐ 권장 - Node.js 20.8.1 내장 (StreamDock 3.10.188.226+) - log4js 로깅 시스템 - 파일 기반 로깅 지원 - WebSocket 재연결 로직 ### 3. Node.js SDK (SDNodeJsSDK) - Legacy - 구 버전 Node.js SDK - V2 사용 권장 ### 4. Vue.js SDK (SDVueSDK) - Vue.js 프레임워크 기반 - 반응형 UI 컴포넌트 - TypeScript 지원 ### 5. C++ SDK (StreamDockCPPSDK) - 네이티브 C++ 구현 - 고성능 플러그인 - Visual Studio 2022 지원 ### 6. Qt SDK (SDQtSDK) - Qt 프레임워크 기반 - 크로스 플랫폼 UI - C++ 기반 ### 7. Python SDK (SDPythonSDK) - 순수 Python 구현 - PyInstaller로 exe 패키징 - 타입 힌팅 지원 --- ## 플러그인이 수신하는 이벤트 ### 공통 이벤트 (Plugin & Property Inspector) #### `didReceiveSettings` 설정이 변경될 때 트리거됩니다. **파라미터:** - `action`: 액션 식별자 - `context`: 컨텍스트 값 - `device`: 장치 정보 - `payload`: 설정 데이터 및 좌표 포함 **예시:** ```json { "event": "didReceiveSettings", "action": "com.example.plugin.action", "context": "unique_context_id", "device": "device_id", "payload": { "settings": { "key": "value" }, "coordinates": { "column": 0, "row": 0 } } } ``` #### `didReceiveGlobalSettings` 전역 설정이 변경될 때 트리거됩니다. **파라미터:** - `payload`: 전역 설정 데이터 --- ### 플러그인 전용 이벤트 #### 입력 이벤트 ##### `keyDown` 사용자가 키를 누를 때 발생합니다. **파라미터:** - `action`: 액션 식별자 - `context`: 컨텍스트 값 - `device`: 장치 ID - `payload`: - `settings`: 액션 설정 - `coordinates`: 키 좌표 - `state`: 현재 상태 (0 또는 1) - `isInMultiAction`: Multi-Action 여부 **예시:** ```json { "event": "keyDown", "action": "com.example.plugin.action", "context": "context_id", "device": "device_id", "payload": { "settings": {}, "coordinates": {"column": 2, "row": 1}, "state": 0, "isInMultiAction": false } } ``` ##### `keyUp` 사용자가 키를 뗄 때 발생합니다. (파라미터는 `keyDown`과 동일) ##### `dialDown` 다이얼(엔코더)을 누를 때 발생합니다. ##### `dialUp` 다이얼(엔코더)을 뗄 때 발생합니다. ##### `dialRotate` 다이얼(엔코더)을 회전할 때 발생합니다. **파라미터:** - `payload.ticks`: 회전 틱 수 (양수=시계방향, 음수=반시계방향) - `payload.pressed`: 다이얼이 눌려진 상태인지 여부 **예시:** ```json { "event": "dialRotate", "action": "com.example.plugin.action", "context": "context_id", "device": "device_id", "payload": { "ticks": 3, "pressed": false, "settings": {} } } ``` --- #### 라이프사이클 이벤트 ##### `willAppear` 액션 인스턴스가 화면에 나타날 때 발생합니다. **발생 시점:** - 시작 시 - 프로필 전환 시 - 키 할당 시 **파라미터:** - `action`: 액션 식별자 - `context`: 컨텍스트 값 - `device`: 장치 ID - `payload`: - `settings`: 저장된 설정 - `coordinates`: 버튼 위치 - `state`: 현재 상태 - `isInMultiAction`: Multi-Action 여부 **예시:** ```json { "event": "willAppear", "action": "com.mirabox.streamingle.optitrack_marker_toggle", "context": "button_context_123", "device": "streamdock_device_1", "payload": { "settings": { "currentState": 0 }, "coordinates": {"column": 0, "row": 0}, "state": 0, "isInMultiAction": false } } ``` ##### `willDisappear` 프로필 전환 또는 액션 삭제 시 트리거됩니다. ##### `titleParametersDidChange` 사용자가 제목 또는 제목 파라미터를 수정할 때 발생합니다. **포함 정보:** - 폰트 설정 - 정렬 방식 - 제목 텍스트 --- #### 시스템 이벤트 ##### `deviceDidConnect` 장치가 연결될 때 발생합니다. **파라미터:** - `device`: 장치 ID - `deviceInfo`: 장치 타입 및 크기 정보 ##### `deviceDidDisconnect` 장치 연결이 해제될 때 발생합니다. ##### `applicationDidLaunch` 지정된 애플리케이션이 실행될 때 발생합니다. **식별자:** - macOS: Bundle ID - Windows: .exe 파일명 ##### `applicationDidTerminate` 지정된 애플리케이션이 종료될 때 발생합니다. ##### `systemDidWakeUp` 컴퓨터가 절전 모드에서 깨어날 때 발생합니다. **주의**: 여러 번 발생할 수 있음 --- #### Property Inspector 이벤트 ##### `propertyInspectorDidAppear` Property Inspector가 표시될 때 발생합니다. ##### `propertyInspectorDidDisappear` Property Inspector가 숨겨질 때 발생합니다. ##### `sendToPlugin` Property Inspector에서 플러그인으로 데이터를 전송합니다. **예시 (Property Inspector → Plugin):** ```javascript // Property Inspector sendToPlugin('update_settings', { value: 123 }); // Plugin websocket.onmessage = (event) => { const jsonObj = JSON.parse(event.data); if (jsonObj.event === 'sendToPlugin') { const payload = jsonObj.payload; console.log('Received:', payload); } }; ``` --- ## 플러그인이 전송하는 이벤트 ### 공통 이벤트 (Plugin & Property Inspector) #### `setSettings` 액션 인스턴스의 데이터를 영구적으로 저장합니다. **구조:** ```json { "event": "setSettings", "context": "context_id", "payload": { "key1": "value1", "key2": 123 } } ``` **사용 예:** ```javascript function saveSettings(context, settings) { const message = { event: 'setSettings', context: context, payload: settings }; websocket.send(JSON.stringify(message)); } ``` #### `getSettings` 저장된 설정을 요청합니다. `didReceiveSettings` 이벤트로 응답을 받습니다. **구조:** ```json { "event": "getSettings", "context": "context_id" } ``` #### `setGlobalSettings` 플러그인 전역 설정을 저장합니다. 모든 액션에서 접근 가능합니다. **구조:** ```json { "event": "setGlobalSettings", "context": "plugin_uuid", "payload": { "globalKey": "globalValue" } } ``` #### `getGlobalSettings` 전역 설정을 요청합니다. `didReceiveGlobalSettings`로 응답을 받습니다. #### `openUrl` 기본 브라우저에서 URL을 엽니다. **구조:** ```json { "event": "openUrl", "payload": { "url": "https://example.com" } } ``` #### `logMessage` 디버그 메시지를 로그 창에 작성합니다. **구조:** ```json { "event": "logMessage", "payload": { "message": "Debug message here" } } ``` --- ### 플러그인 전용 이벤트 #### `setTitle` 액션 인스턴스의 제목을 동적으로 업데이트합니다. **파라미터:** - `context`: 컨텍스트 ID - `payload.title`: 제목 텍스트 (줄바꿈: `\n`) - `payload.target`: 대상 (0=하드웨어+소프트웨어, 1=하드웨어만, 2=소프트웨어만) - `payload.state`: 상태 (다중 상태 액션용) **예시:** ```javascript function setButtonTitle(context, title) { const message = { event: 'setTitle', context: context, payload: { title: title, target: 0 } }; websocket.send(JSON.stringify(message)); } // 사용 setButtonTitle(context, '마커\nON'); ``` #### `setImage` 액션의 이미지를 변경합니다. **지원 형식:** - Base64 인코딩 이미지 - SVG 형식 **파라미터:** - `context`: 컨텍스트 ID - `payload.image`: Base64 또는 SVG 문자열 - `payload.target`: 대상 - `payload.state`: 상태 **예시:** ```javascript function setButtonImage(context, imageBase64) { const message = { event: 'setImage', context: context, payload: { image: imageBase64, target: 0 } }; websocket.send(JSON.stringify(message)); } ``` #### `showAlert` 액션에 임시 경고 아이콘을 표시합니다. **구조:** ```json { "event": "showAlert", "context": "context_id" } ``` #### `showOk` 액션에 임시 체크마크 확인 아이콘을 표시합니다. **구조:** ```json { "event": "showOk", "context": "context_id" } ``` #### `setState` 다중 상태 액션의 상태를 변경합니다. **파라미터:** - `context`: 컨텍스트 ID - `payload.state`: 상태 번호 (0, 1, 2, ...) **예시:** ```javascript function setButtonState(context, state) { const message = { event: 'setState', context: context, payload: { state: state } }; websocket.send(JSON.stringify(message)); } // 사용: ON(0) → OFF(1) 토글 const newState = currentState === 0 ? 1 : 0; setButtonState(context, newState); ``` #### `sendToPropertyInspector` 플러그인에서 Property Inspector로 데이터를 전송합니다. **예시 (Plugin → Property Inspector):** ```javascript // Plugin function sendToPI(context, action, payload) { const message = { event: 'sendToPropertyInspector', action: action, context: context, payload: payload }; websocket.send(JSON.stringify(message)); } // Property Inspector window.connectElgatoStreamDeckSocket = (inPort, inUUID, inEvent, inInfo) => { websocket.onmessage = (evt) => { const jsonObj = JSON.parse(evt.data); if (jsonObj.event === 'sendToPropertyInspector') { console.log('Received from plugin:', jsonObj.payload); } }; }; ``` --- ## 메시지 구조 ### 표준 메시지 형식 모든 WebSocket 메시지는 다음 표준 JSON 구조를 따릅니다: ```json { "event": "이벤트_이름", "action": "com.developer.plugin.action_id", "context": "unique_context_id", "device": "device_id", "payload": { // 이벤트별 데이터 } } ``` ### 필드 설명 - **event**: 이벤트 타입 (필수) - **action**: 액션 UUID (일부 이벤트에서 선택) - **context**: 고유 컨텍스트 식별자 (필수) - **device**: 장치 ID (장치 관련 이벤트) - **payload**: 이벤트별 데이터 객체 --- ## 버전 요구사항 ### StreamDock 소프트웨어 버전 #### Windows - **최소 버전**: `3.10.188.226` 이상 - **Node.js 내장**: 버전 20.8.1 - **manifest.json 설정**: ```json { "Nodejs": { "Version": "20" } } ``` #### macOS - **최소 버전**: `3.10.189.0313` (Node.js 내장 예정) - 현재는 외부 Node.js 설치 필요 ### SDK 버전 호환성 | SDK | 최소 SW 버전 | Node.js 버전 | 권장 사용 | |-----|-------------|-------------|----------| | SDNodeJsSDKV2 | 3.10.188.226 | 20.8.1 | ✅ 권장 | | SDNodeJsSDK | 모든 버전 | 외부 설치 | ⚠️ Legacy | | SDJavaScriptSDK | 모든 버전 | N/A | ✅ 간단한 플러그인 | | SDVueSDK | 3.10.188.226 | 20.8.1 | ✅ 복잡한 UI | | StreamDockCPPSDK | 모든 버전 | N/A | ✅ 고성능 | | SDPythonSDK | 모든 버전 | N/A | ✅ Python 개발자 | --- ## 참고 자료 ### 공식 문서 - **메인**: https://sdk.key123.vip/ - **Getting Started**: https://sdk.key123.vip/en/guide/overview.html - **수신 이벤트**: https://sdk.key123.vip/en/guide/events-received.html - **전송 이벤트**: https://sdk.key123.vip/en/guide/events-sent.html - **Manifest 문서**: https://sdk.key123.vip/en/guide/manifest.html ### GitHub - **SDK 리포지토리**: https://github.com/MiraboxSpace/StreamDock-Plugin-SDK - **플러그인 예제**: https://github.com/MiraboxSpace/StreamDock-Plugins ### 커뮤니티 - **Email**: service@key123.vip - **Discord**: 공식 Discord 서버 - **플랫폼**: https://space.key123.vip/ (플러그인 마켓플레이스) ### 라이선스 - **MIT License** - 오픈소스 --- **문서 버전**: 1.0 **최종 업데이트**: 2025-01-27 **백업 목적**: AI 환각 방지 및 오프라인 참조