419 lines
14 KiB
JavaScript
419 lines
14 KiB
JavaScript
/**
|
|
* Streamingle Camera Controller - Property Inspector (단순화 버전)
|
|
* Plugin Main을 통해 Unity와 통신
|
|
*/
|
|
|
|
// Global variables
|
|
let websocket = null;
|
|
// 전역 uuid와 actionContext를 명확히 구분
|
|
let uuid = null; // StreamDeck에서 받은 context(uuid)
|
|
let actionContext = null; // actionInfo.context
|
|
let settings = {};
|
|
|
|
// Context별 설정 관리
|
|
const contextSettings = new Map();
|
|
let currentActionContext = null;
|
|
|
|
// Unity 연결 상태 (Plugin Main에서 받아옴)
|
|
let isUnityConnected = false;
|
|
let cameraData = [];
|
|
let currentCamera = 0;
|
|
|
|
// DOM elements
|
|
let statusDot = null;
|
|
let connectionStatus = null;
|
|
let cameraSelect = null;
|
|
let currentCameraDisplay = null;
|
|
let refreshButton = null;
|
|
|
|
// 화면에 로그를 표시하는 함수
|
|
function logToScreen(msg, color = "#fff") {
|
|
let logDiv = document.getElementById('logArea');
|
|
if (!logDiv) {
|
|
console.log('로그 영역을 찾을 수 없음');
|
|
return;
|
|
}
|
|
const line = document.createElement('div');
|
|
line.style.color = color;
|
|
line.textContent = `[${new Date().toLocaleTimeString()}] ${msg}`;
|
|
logDiv.appendChild(line);
|
|
logDiv.scrollTop = logDiv.scrollHeight;
|
|
}
|
|
|
|
// 기존 console.log/console.error를 화면에도 출력
|
|
const origLog = console.log;
|
|
console.log = function(...args) {
|
|
origLog.apply(console, args);
|
|
logToScreen(args.map(a => (typeof a === 'object' ? JSON.stringify(a) : a)).join(' '), '#0f0');
|
|
};
|
|
const origErr = console.error;
|
|
console.error = function(...args) {
|
|
origErr.apply(console, args);
|
|
logToScreen(args.map(a => (typeof a === 'object' ? JSON.stringify(a) : a)).join(' '), '#f55');
|
|
};
|
|
|
|
console.log('🔧 Property Inspector script loaded');
|
|
|
|
// Initialize when DOM is loaded
|
|
document.addEventListener('DOMContentLoaded', function() {
|
|
console.log('📋 Property Inspector 초기화');
|
|
initializePropertyInspector();
|
|
});
|
|
|
|
// Initialize Property Inspector
|
|
function initializePropertyInspector() {
|
|
// Get DOM elements
|
|
statusDot = document.getElementById('statusDot');
|
|
connectionStatus = document.getElementById('connection-status');
|
|
cameraSelect = document.getElementById('camera-select');
|
|
currentCameraDisplay = document.getElementById('current-camera');
|
|
refreshButton = document.getElementById('refresh-button');
|
|
|
|
// Setup event listeners
|
|
if (cameraSelect) {
|
|
cameraSelect.addEventListener('change', onCameraSelectionChanged);
|
|
}
|
|
|
|
if (refreshButton) {
|
|
refreshButton.addEventListener('click', onRefreshClicked);
|
|
}
|
|
|
|
console.log('✅ Property Inspector 준비 완료');
|
|
}
|
|
|
|
// Send message to plugin
|
|
function sendToPlugin(command, data = {}) {
|
|
if (!websocket) {
|
|
console.error('❌ WebSocket not available');
|
|
return;
|
|
}
|
|
if (!uuid) {
|
|
console.error('❌ UUID not available');
|
|
return;
|
|
}
|
|
try {
|
|
const message = {
|
|
action: 'com.mirabox.streamingle.camera', // manifest.json의 Action UUID
|
|
event: 'sendToPlugin',
|
|
context: uuid, // 아이템과 동일한 패턴
|
|
payload: {
|
|
command,
|
|
...data
|
|
}
|
|
};
|
|
websocket.send(JSON.stringify(message));
|
|
console.log('📤 Message sent to plugin:', command, data, 'context:', uuid);
|
|
} catch (error) {
|
|
console.error('❌ Failed to send message to plugin:', error);
|
|
}
|
|
}
|
|
|
|
// Update connection status display
|
|
function updateConnectionStatus(isConnected) {
|
|
console.log('🔄 Connection status update:', isConnected);
|
|
|
|
// 전역 변수도 업데이트
|
|
isUnityConnected = isConnected;
|
|
|
|
if (statusDot) {
|
|
statusDot.className = `dot ${isConnected ? 'green' : 'red'}`;
|
|
}
|
|
|
|
if (connectionStatus) {
|
|
connectionStatus.textContent = isConnected ? 'Unity 연결됨' : 'Unity 연결 안됨';
|
|
connectionStatus.className = isConnected ? 'connected' : 'disconnected';
|
|
}
|
|
|
|
if (cameraSelect) {
|
|
cameraSelect.disabled = !isConnected;
|
|
}
|
|
|
|
if (refreshButton) {
|
|
refreshButton.disabled = !isConnected;
|
|
}
|
|
}
|
|
|
|
// Update camera data display
|
|
function updateCameraData(cameraDataParam, currentCamera) {
|
|
console.log('📹 Camera data update:', cameraDataParam, currentCamera);
|
|
if (cameraSelect && cameraDataParam) {
|
|
cameraSelect.innerHTML = '';
|
|
let cameras = cameraDataParam;
|
|
if (cameraDataParam.cameras) {
|
|
cameras = cameraDataParam.cameras;
|
|
} else if (Array.isArray(cameraDataParam)) {
|
|
cameras = cameraDataParam;
|
|
}
|
|
console.log('📹 처리할 카메라 배열:', cameras);
|
|
if (cameras && cameras.length > 0) {
|
|
cameraData = cameras;
|
|
console.log('💾 전역 cameraData 저장됨:', cameraData.length + '개');
|
|
cameras.forEach((camera, index) => {
|
|
const option = document.createElement('option');
|
|
option.value = index;
|
|
option.textContent = `카메라 ${index + 1}`;
|
|
if (camera.name) {
|
|
option.textContent += ` (${camera.name})`;
|
|
}
|
|
cameraSelect.appendChild(option);
|
|
});
|
|
// settings.cameraIndex를 우선 적용
|
|
let selectedIndex = currentCamera;
|
|
if (typeof settings.cameraIndex === 'number') {
|
|
selectedIndex = settings.cameraIndex;
|
|
}
|
|
if (typeof selectedIndex === 'number') {
|
|
cameraSelect.value = selectedIndex;
|
|
}
|
|
cameraSelect.disabled = false;
|
|
console.log('✅ 카메라 목록 업데이트 완료:', cameras.length + '개');
|
|
} else {
|
|
console.log('⚠️ 카메라 데이터가 없거나 빈 배열');
|
|
cameraSelect.disabled = true;
|
|
}
|
|
}
|
|
updateCurrentCameraDisplay(currentCamera);
|
|
}
|
|
|
|
// Update current camera display
|
|
function updateCurrentCameraDisplay(currentCamera) {
|
|
if (currentCameraDisplay) {
|
|
if (typeof currentCamera === 'number') {
|
|
currentCameraDisplay.textContent = `현재 카메라: ${currentCamera + 1}`;
|
|
} else {
|
|
currentCameraDisplay.textContent = '현재 카메라: -';
|
|
}
|
|
}
|
|
}
|
|
|
|
// Camera selection changed
|
|
function onCameraSelectionChanged() {
|
|
if (!cameraSelect) return;
|
|
|
|
const selectedIndex = parseInt(cameraSelect.value);
|
|
if (isNaN(selectedIndex)) return;
|
|
|
|
console.log('📹 카메라 선택 변경:', selectedIndex);
|
|
|
|
// Plugin Main에 카메라 변경 요청
|
|
sendToPlugin('switch_camera', { cameraIndex: selectedIndex });
|
|
|
|
// 설정 저장
|
|
const currentSettings = getContextSettings(uuid);
|
|
const newSettings = {
|
|
...currentSettings,
|
|
cameraIndex: selectedIndex
|
|
};
|
|
saveContextSettings(newSettings);
|
|
|
|
// 버튼 제목 업데이트 요청
|
|
sendToPlugin('update_title', { cameraIndex: selectedIndex });
|
|
}
|
|
|
|
// Refresh button clicked
|
|
function onRefreshClicked() {
|
|
console.log('🔄 카메라 목록 새로고침 요청');
|
|
sendToPlugin('get_camera_list');
|
|
}
|
|
|
|
// Handle messages from plugin
|
|
function handleMessage(jsonObj) {
|
|
console.log('📨 Property Inspector 메시지 수신:', jsonObj.event);
|
|
|
|
// 플러그인에서 오는 메시지는 event가 sendToPropertyInspector이고, 실제 타입은 payload.event에 있음
|
|
if (jsonObj.event === 'sendToPropertyInspector' && jsonObj.payload && jsonObj.payload.event) {
|
|
const innerEvent = jsonObj.payload.event;
|
|
console.log('📨 내부 이벤트:', innerEvent, 'payload:', jsonObj.payload);
|
|
|
|
switch (innerEvent) {
|
|
case 'unity_connected':
|
|
console.log('✅ Unity 연결 상태 업데이트');
|
|
updateConnectionStatus(true);
|
|
break;
|
|
case 'unity_disconnected':
|
|
console.log('❌ Unity 연결 해제 상태 업데이트');
|
|
updateConnectionStatus(false);
|
|
break;
|
|
case 'camera_list':
|
|
console.log('📹 카메라 목록 수신');
|
|
updateCameraData(jsonObj.payload.cameras, jsonObj.payload.currentIndex);
|
|
break;
|
|
case 'camera_changed':
|
|
console.log('📹 카메라 변경 알림');
|
|
updateCurrentCameraDisplay(jsonObj.payload.cameraIndex);
|
|
break;
|
|
case 'item_list':
|
|
console.log('⚠️ 아이템 데이터를 받았지만 카메라 컨트롤러에서는 무시함');
|
|
break;
|
|
case 'event_list':
|
|
console.log('⚠️ 이벤트 데이터를 받았지만 카메라 컨트롤러에서는 무시함');
|
|
break;
|
|
case 'avatar_outfit_list':
|
|
console.log('⚠️ 아바타 데이터를 받았지만 카메라 컨트롤러에서는 무시함');
|
|
break;
|
|
case 'avatar_outfit_changed':
|
|
console.log('⚠️ 아바타 변경 데이터를 받았지만 카메라 컨트롤러에서는 무시함');
|
|
break;
|
|
default:
|
|
console.log('❓ 알 수 없는 내부 이벤트:', innerEvent);
|
|
}
|
|
return;
|
|
}
|
|
|
|
// 기존 방식도 유지
|
|
switch (jsonObj.event) {
|
|
case 'unity_connected':
|
|
console.log('✅ Unity 연결 상태 업데이트');
|
|
updateConnectionStatus(true);
|
|
break;
|
|
case 'unity_disconnected':
|
|
console.log('❌ Unity 연결 해제 상태 업데이트');
|
|
updateConnectionStatus(false);
|
|
break;
|
|
case 'camera_list':
|
|
console.log('📹 카메라 목록 수신');
|
|
if (jsonObj.payload && jsonObj.payload.cameras) {
|
|
updateCameraData(jsonObj.payload.cameras, jsonObj.payload.currentIndex);
|
|
}
|
|
break;
|
|
case 'camera_changed':
|
|
console.log('📹 카메라 변경 알림');
|
|
if (jsonObj.payload && typeof jsonObj.payload.cameraIndex === 'number') {
|
|
updateCurrentCameraDisplay(jsonObj.payload.cameraIndex);
|
|
}
|
|
break;
|
|
case 'didReceiveSettings':
|
|
console.log('⚙️ 설정 수신');
|
|
if (jsonObj.payload && jsonObj.context) {
|
|
const newSettings = jsonObj.payload.settings || {};
|
|
contextSettings.set(jsonObj.context, newSettings);
|
|
// 카메라 인덱스가 있으면 선택
|
|
if (typeof newSettings.cameraIndex === 'number' && cameraSelect) {
|
|
cameraSelect.value = newSettings.cameraIndex;
|
|
updateCurrentCameraDisplay(newSettings.cameraIndex);
|
|
}
|
|
}
|
|
break;
|
|
default:
|
|
console.log('❓ 알 수 없는 메시지 타입:', jsonObj.event);
|
|
}
|
|
}
|
|
|
|
// StreamDeck SDK 연결
|
|
function connectElgatoStreamDeckSocket(inPort, inPropertyInspectorUUID, inRegisterEvent, inInfo, inActionInfo) {
|
|
console.log('🔌 Property Inspector StreamDeck 연결');
|
|
console.log('📡 포트:', inPort, 'UUID:', inPropertyInspectorUUID);
|
|
|
|
uuid = inPropertyInspectorUUID;
|
|
|
|
// Parse action info
|
|
try {
|
|
if (inActionInfo) {
|
|
const actionInfo = JSON.parse(inActionInfo);
|
|
actionContext = actionInfo.context;
|
|
settings = actionInfo.payload?.settings || {};
|
|
|
|
console.log('⚙️ 초기 설정:', settings);
|
|
|
|
// 카메라 인덱스가 있으면 선택
|
|
if (typeof settings.cameraIndex === 'number' && cameraSelect) {
|
|
cameraSelect.value = settings.cameraIndex;
|
|
updateCurrentCameraDisplay(settings.cameraIndex);
|
|
}
|
|
}
|
|
} catch (error) {
|
|
console.error('❌ ActionInfo 파싱 오류:', error);
|
|
}
|
|
|
|
// WebSocket 연결
|
|
if (!websocket) {
|
|
websocket = new WebSocket('ws://localhost:' + inPort);
|
|
|
|
websocket.onopen = function() {
|
|
console.log('✅ Property Inspector StreamDeck 연결됨');
|
|
|
|
// StreamDeck에 등록
|
|
websocket.send(JSON.stringify({
|
|
event: inRegisterEvent,
|
|
uuid: inPropertyInspectorUUID
|
|
}));
|
|
|
|
// Unity 상태 요청
|
|
setTimeout(() => {
|
|
sendToPlugin('get_unity_status');
|
|
}, 500);
|
|
};
|
|
|
|
websocket.onmessage = function(evt) {
|
|
try {
|
|
const jsonObj = JSON.parse(evt.data);
|
|
handleMessage(jsonObj);
|
|
} catch (error) {
|
|
console.error('❌ 메시지 파싱 오류:', error);
|
|
}
|
|
};
|
|
|
|
websocket.onclose = function() {
|
|
console.log('❌ Property Inspector StreamDeck 연결 끊어짐');
|
|
websocket = null;
|
|
};
|
|
|
|
websocket.onerror = function(error) {
|
|
console.error('❌ Property Inspector StreamDeck 연결 오류:', error);
|
|
};
|
|
}
|
|
}
|
|
|
|
// 설정 관리 헬퍼 함수들
|
|
function getContextSettings(context) {
|
|
if (!context) return {};
|
|
return contextSettings.get(context) || {};
|
|
}
|
|
|
|
// context별 설정 저장 (StreamDeck 표준 setSettings 사용)
|
|
function saveContextSettings(newSettings) {
|
|
if (!uuid || !websocket) return;
|
|
websocket.send(JSON.stringify({
|
|
action: 'com.mirabox.streamingle.camera',
|
|
event: 'setSettings',
|
|
context: uuid,
|
|
payload: newSettings
|
|
}));
|
|
contextSettings.set(uuid, newSettings); // 로컬에도 저장
|
|
}
|
|
|
|
// context별 설정 불러오기 (StreamDeck 표준 getSettings 사용)
|
|
function requestSettings() {
|
|
if (!uuid || !websocket) return;
|
|
websocket.send(JSON.stringify({
|
|
action: 'com.mirabox.streamingle.camera',
|
|
event: 'getSettings',
|
|
context: uuid
|
|
}));
|
|
}
|
|
|
|
// Property Inspector 초기화 시 context별 설정 요청
|
|
function initializePropertyInspector() {
|
|
// Get DOM elements
|
|
statusDot = document.getElementById('statusDot');
|
|
connectionStatus = document.getElementById('connection-status');
|
|
cameraSelect = document.getElementById('camera-select');
|
|
currentCameraDisplay = document.getElementById('current-camera');
|
|
refreshButton = document.getElementById('refresh-button');
|
|
|
|
// Setup event listeners
|
|
if (cameraSelect) {
|
|
cameraSelect.addEventListener('change', onCameraSelectionChanged);
|
|
}
|
|
|
|
if (refreshButton) {
|
|
refreshButton.addEventListener('click', onRefreshClicked);
|
|
}
|
|
|
|
// 현재 액션의 컨텍스트가 있으면 설정 요청
|
|
if (uuid) {
|
|
requestSettings();
|
|
}
|
|
|
|
console.log('✅ Property Inspector 준비 완료');
|
|
}
|