Streamingle_URP/Streamdeck/STREAMDOCK_SDK_REFERENCE.md
2025-10-28 00:02:17 +09:00

756 lines
19 KiB (Stored with Git LFS)
Markdown

# 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 환각 방지 및 오프라인 참조