2025-10-28 00:02:17 +09:00

2026 lines
96 KiB
JavaScript

// MiraBox StreamDock 플러그인 - 브라우저 기반
console.log('🚀 Streamingle 플러그인 시작 (브라우저 기반)');
// Global variables
let websocket = null;
let buttonContexts = new Map(); // 각 버튼의 컨텍스트별 설정 저장
let unitySocket = null;
let isUnityConnected = false;
let cameraList = []; // 카메라 목록 저장
let itemList = []; // 아이템 목록 저장
let eventList = []; // 이벤트 목록 저장
let avatarOutfitList = []; // 아바타 의상 목록 저장
// StreamDock 연결 함수 (브라우저 기반)
function connectElgatoStreamDeckSocket(inPort, inUUID, inEvent, inInfo, inActionInfo) {
console.log('🔌 StreamDock 연결 시작 (브라우저)');
console.log('📡 포트:', inPort, 'UUID:', inUUID, '이벤트:', inEvent);
// Parse action info to get initial settings
try {
if (inActionInfo) {
const actionInfo = JSON.parse(inActionInfo);
const context = actionInfo.context;
const settings = actionInfo.payload?.settings || {};
// 각 컨텍스트별로 설정 저장
buttonContexts.set(context, settings);
console.log('⚙️ 초기 설정 저장:', settings);
}
} catch (error) {
console.error('❌ ActionInfo 파싱 오류:', error);
}
// WebSocket 연결
if (!websocket) {
websocket = new WebSocket('ws://localhost:' + inPort);
websocket.onopen = function() {
console.log('✅ StreamDock 연결됨 (브라우저)');
// StreamDock에 등록
websocket.send(JSON.stringify({
event: inEvent,
uuid: inUUID
}));
// Unity 연결 시도
setTimeout(() => {
connectToUnity();
}, 1000);
// Unity 연결 재시도 (5초마다)
setInterval(() => {
if (!isUnityConnected) {
console.log('🔄 Unity 재연결 시도...');
connectToUnity();
}
}, 5000);
};
websocket.onmessage = function(evt) {
try {
const jsonObj = JSON.parse(evt.data);
console.log('📨 StreamDock 메시지 수신:', jsonObj.event);
console.log('📋 전체 메시지 데이터:', JSON.stringify(jsonObj, null, 2));
switch(jsonObj.event) {
case 'didReceiveSettings':
if (jsonObj.payload && jsonObj.context) {
const newSettings = jsonObj.payload.settings || {};
// actionType이 없으면 action UUID로 판단해서 강제 설정
if (!newSettings.actionType && jsonObj.action) {
console.log('⚠️ actionType 없음! action UUID로 재설정:', jsonObj.action);
if (jsonObj.action === 'com.mirabox.streamingle.optitrack_marker_toggle') {
newSettings.actionType = 'optitrack_marker_toggle';
console.log('✅ 마커 버튼으로 강제 설정됨');
// StreamDock SDK에 저장
if (websocket) {
const setSettingsMessage = {
event: 'setSettings',
context: jsonObj.context,
payload: newSettings
};
websocket.send(JSON.stringify(setSettingsMessage));
console.log('💾 강제 설정 저장:', newSettings);
}
} else if (jsonObj.action === 'com.mirabox.streamingle.motion_recording_toggle') {
newSettings.actionType = 'motion_recording_toggle';
console.log('🎬 모션 녹화 버튼으로 강제 설정됨');
// StreamDock SDK에 저장
if (websocket) {
const setSettingsMessage = {
event: 'setSettings',
context: jsonObj.context,
payload: newSettings
};
websocket.send(JSON.stringify(setSettingsMessage));
console.log('💾 강제 설정 저장:', newSettings);
}
} else if (jsonObj.action === 'com.mirabox.streamingle.optitrack_reconnect') {
newSettings.actionType = 'optitrack_reconnect';
console.log('🔄 OptiTrack 재접속 버튼으로 강제 설정됨');
// StreamDock SDK에 저장
if (websocket) {
const setSettingsMessage = {
event: 'setSettings',
context: jsonObj.context,
payload: newSettings
};
websocket.send(JSON.stringify(setSettingsMessage));
console.log('💾 강제 설정 저장:', newSettings);
}
} else if (jsonObj.action === 'com.mirabox.streamingle.facial_motion_reconnect') {
newSettings.actionType = 'facial_motion_reconnect';
console.log('😊 Facial Motion 재접속 버튼으로 강제 설정됨');
// StreamDock SDK에 저장
if (websocket) {
const setSettingsMessage = {
event: 'setSettings',
context: jsonObj.context,
payload: newSettings
};
websocket.send(JSON.stringify(setSettingsMessage));
console.log('💾 강제 설정 저장:', newSettings);
}
} else if (jsonObj.action === 'com.mirabox.streamingle.screenshot') {
newSettings.actionType = 'screenshot';
console.log('📸 스크린샷 버튼으로 강제 설정됨');
// StreamDock SDK에 저장
if (websocket) {
const setSettingsMessage = {
event: 'setSettings',
context: jsonObj.context,
payload: newSettings
};
websocket.send(JSON.stringify(setSettingsMessage));
console.log('💾 강제 설정 저장:', newSettings);
}
} else if (jsonObj.action === 'com.mirabox.streamingle.screenshot_alpha') {
newSettings.actionType = 'screenshot_alpha';
console.log('📷 알파 스크린샷 버튼으로 강제 설정됨');
// StreamDock SDK에 저장
if (websocket) {
const setSettingsMessage = {
event: 'setSettings',
context: jsonObj.context,
payload: newSettings
};
websocket.send(JSON.stringify(setSettingsMessage));
console.log('💾 강제 설정 저장:', newSettings);
}
} else if (jsonObj.action === 'com.mirabox.streamingle.item') {
newSettings.actionType = 'item';
} else if (jsonObj.action === 'com.mirabox.streamingle.event') {
newSettings.actionType = 'event';
} else if (jsonObj.action === 'com.mirabox.streamingle.avatar_outfit') {
newSettings.actionType = 'avatar_outfit';
} else {
newSettings.actionType = 'camera';
}
}
buttonContexts.set(jsonObj.context, newSettings);
console.log('⚙️ 설정 업데이트:', newSettings);
updateButtonTitle(jsonObj.context);
}
break;
case 'willAppear':
console.log('============================================');
console.log('👀 버튼 나타남 이벤트');
console.log('🔍 Context:', jsonObj.context);
console.log('🔍 Action UUID:', jsonObj.action);
let settings = jsonObj.payload?.settings || {};
console.log('⚙️ 초기 설정:', JSON.stringify(settings));
// action UUID로 actionType 결정
if (jsonObj.action === 'com.mirabox.streamingle.item') {
settings.actionType = 'item';
console.log('🎯 아이템 컨트롤러 등록');
} else if (jsonObj.action === 'com.mirabox.streamingle.event') {
settings.actionType = 'event';
console.log('🎯 이벤트 컨트롤러 등록');
} else if (jsonObj.action === 'com.mirabox.streamingle.avatar_outfit') {
settings.actionType = 'avatar_outfit';
console.log('👗 아바타 의상 컨트롤러 등록');
} else if (jsonObj.action === 'com.mirabox.streamingle.optitrack_marker_toggle') {
settings.actionType = 'optitrack_marker_toggle';
console.log('✅✅✅ MARKER BUTTON DETECTED ✅✅✅');
console.log('✅ This is the OptiTrack Marker Toggle button!');
console.log('✅ When clicked, it should send: toggle_optitrack_markers');
// 기본 제목 설정
setButtonTitle(jsonObj.context, '마커\nON');
} else if (jsonObj.action === 'com.mirabox.streamingle.motion_recording_toggle') {
settings.actionType = 'motion_recording_toggle';
console.log('🎬🎬🎬 MOTION RECORDING BUTTON DETECTED 🎬🎬🎬');
console.log('🎬 This is the Motion Recording Toggle button!');
console.log('🎬 When clicked, it should send: toggle_motion_recording');
// 기본 제목 설정
setButtonTitle(jsonObj.context, '녹화\nOFF');
} else if (jsonObj.action === 'com.mirabox.streamingle.optitrack_reconnect') {
settings.actionType = 'optitrack_reconnect';
console.log('🔄🔄🔄 OPTITRACK RECONNECT BUTTON DETECTED 🔄🔄🔄');
console.log('🔄 This is the OptiTrack Reconnect button!');
console.log('🔄 When clicked, it should send: reconnect_optitrack');
// 기본 제목 설정
setButtonTitle(jsonObj.context, '옵티트랙\n재접속');
} else if (jsonObj.action === 'com.mirabox.streamingle.facial_motion_reconnect') {
settings.actionType = 'facial_motion_reconnect';
console.log('😊😊😊 FACIAL MOTION RECONNECT BUTTON DETECTED 😊😊😊');
console.log('😊 This is the Facial Motion Reconnect button!');
console.log('😊 When clicked, it should send: reconnect_facial_motion');
// 기본 제목 설정
setButtonTitle(jsonObj.context, '페이셜\n재접속');
} else if (jsonObj.action === 'com.mirabox.streamingle.screenshot') {
settings.actionType = 'screenshot';
console.log('📸📸📸 SCREENSHOT BUTTON DETECTED 📸📸📸');
console.log('📸 This is the Screenshot button!');
console.log('📸 When clicked, it should send: capture_screenshot');
// 기본 제목 설정
setButtonTitle(jsonObj.context, '스크린샷');
} else if (jsonObj.action === 'com.mirabox.streamingle.screenshot_alpha') {
settings.actionType = 'screenshot_alpha';
console.log('📷📷📷 ALPHA SCREENSHOT BUTTON DETECTED 📷📷📷');
console.log('📷 This is the Alpha Screenshot button!');
console.log('📷 When clicked, it should send: capture_alpha_screenshot');
// 기본 제목 설정
setButtonTitle(jsonObj.context, '알파\n스크린샷');
} else {
settings.actionType = 'camera';
console.log('📹 카메라 컨트롤러 등록 (기본값)');
console.log('📹 This button will send camera switch messages');
}
console.log('🎯 최종 actionType:', settings.actionType);
buttonContexts.set(jsonObj.context, settings);
// StreamDock SDK에도 설정 저장
if (websocket) {
const setSettingsMessage = {
event: 'setSettings',
context: jsonObj.context,
payload: settings
};
websocket.send(JSON.stringify(setSettingsMessage));
console.log('💾 StreamDock SDK에 설정 저장:', settings);
}
console.log('💾 설정 저장 완료');
console.log('============================================');
updateButtonTitle(jsonObj.context);
// Unity가 이미 연결되어 있다면 Property Inspector에 상태 전송
if (isUnityConnected) {
const settings = getCurrentSettings(jsonObj.context);
const actionType = settings.actionType || 'camera';
let actionUUID = 'com.mirabox.streamingle.camera';
if (actionType === 'item') {
actionUUID = 'com.mirabox.streamingle.item';
} else if (actionType === 'event') {
actionUUID = 'com.mirabox.streamingle.event';
} else if (actionType === 'avatar_outfit') {
actionUUID = 'com.mirabox.streamingle.avatar_outfit';
}
console.log('📤 willAppear에서 Unity 연결 상태 전송:', actionType, actionUUID);
sendToPropertyInspector(jsonObj.context, 'unity_connected', { connected: true }, actionUUID);
// 아바타 의상 컨트롤러인 경우 데이터도 함께 전송
if (actionType === 'avatar_outfit') {
console.log('📤 willAppear에서 아바타 데이터 전송:', avatarOutfitList.length, '개');
if (avatarOutfitList.length > 0) {
sendToPropertyInspector(jsonObj.context, 'avatar_outfit_list', {
avatars: avatarOutfitList,
currentIndex: 0
}, actionUUID);
} else {
// 아바타 목록이 없으면 Unity에 요청
console.log('📤 willAppear에서 아바타 목록 요청');
if (unitySocket && unitySocket.readyState === WebSocket.OPEN) {
const message = JSON.stringify({ type: 'get_avatar_outfit_list' });
unitySocket.send(message);
console.log('📤 Unity에 아바타 목록 요청 전송 (willAppear)');
}
}
}
}
break;
case 'keyUp':
console.log('🔘 버튼 클릭됨!');
console.log('🔍 Action UUID:', jsonObj.action);
handleButtonClick(jsonObj.context, jsonObj.action);
break;
case 'sendToPlugin':
console.log('📨 sendToPlugin 이벤트 수신');
console.log(' - context:', jsonObj.context);
console.log(' - action:', jsonObj.action);
console.log(' - payload:', jsonObj.payload);
handlePropertyInspectorMessage(jsonObj.payload, jsonObj.context, jsonObj.action);
break;
case 'setSettings':
console.log('💾 setSettings 이벤트 수신:', jsonObj.payload, 'context:', jsonObj.context);
if (jsonObj.payload && jsonObj.context) {
const currentSettings = getCurrentSettings(jsonObj.context);
console.log('📋 저장 전 설정:', currentSettings);
// actionType을 유지하면서 설정 업데이트
const updatedSettings = { ...currentSettings, ...jsonObj.payload };
setCurrentSettings(jsonObj.context, updatedSettings);
const newSettings = getCurrentSettings(jsonObj.context);
console.log('✅ 설정 저장 완료:', newSettings);
console.log('🎯 액션 타입:', newSettings.actionType);
updateButtonTitle(jsonObj.context);
}
break;
default:
console.log('❓ 알 수 없는 이벤트:', jsonObj.event);
break;
}
} catch (error) {
console.error('❌ 메시지 파싱 오류:', error);
}
};
websocket.onclose = function() {
console.log('❌ StreamDock 연결 끊어짐');
websocket = null;
};
websocket.onerror = function(error) {
console.error('❌ StreamDock 연결 오류:', error);
};
}
}
// Unity 연결 함수 (브라우저 네이티브 WebSocket 사용)
function connectToUnity() {
console.log('🔌 Unity 연결 시도 (브라우저)...');
if (unitySocket) {
unitySocket.close();
}
try {
unitySocket = new WebSocket('ws://localhost:10701');
unitySocket.onopen = function() {
isUnityConnected = true;
console.log('✅ Unity 연결 성공 (브라우저)!');
// 카메라 목록 요청
setTimeout(() => {
const message = JSON.stringify({ type: 'get_camera_list' });
unitySocket.send(message);
console.log('📋 카메라 목록 요청:', message);
}, 100);
// 아이템 목록 요청
setTimeout(() => {
const message = JSON.stringify({ type: 'get_item_list' });
unitySocket.send(message);
console.log('📋 아이템 목록 요청:', message);
}, 200);
// 이벤트 목록 요청
setTimeout(() => {
const message = JSON.stringify({ type: 'get_event_list' });
unitySocket.send(message);
console.log('📋 이벤트 목록 요청:', message);
}, 300);
// 아바타 의상 목록 요청
setTimeout(() => {
const message = JSON.stringify({ type: 'get_avatar_outfit_list' });
unitySocket.send(message);
console.log('📋 아바타 의상 목록 요청:', message);
}, 400);
};
unitySocket.onmessage = function(event) {
try {
const data = JSON.parse(event.data);
console.log('📨 Unity 메시지 수신:', data.type);
console.log('📋 Unity 메시지 전체 데이터:', JSON.stringify(data, null, 2));
handleUnityMessage(data);
} catch (error) {
console.error('❌ Unity 메시지 파싱 오류:', error);
}
};
unitySocket.onclose = function() {
isUnityConnected = false;
unitySocket = null; // 소켓을 null로 설정
console.log('❌ Unity 연결 끊어짐');
// Property Inspector들에게 Unity 연결 해제 알림
for (const context of buttonContexts.keys()) {
// context별로 적절한 action UUID 결정
const settings = getCurrentSettings(context);
const actionType = settings.actionType || 'camera';
let actionUUID = 'com.mirabox.streamingle.camera';
if (actionType === 'item') {
actionUUID = 'com.mirabox.streamingle.item';
} else if (actionType === 'event') {
actionUUID = 'com.mirabox.streamingle.event';
} else if (actionType === 'avatar_outfit') {
actionUUID = 'com.mirabox.streamingle.avatar_outfit';
}
sendToPropertyInspector(context, 'unity_connected', { connected: false }, actionUUID);
}
};
unitySocket.onerror = function(error) {
isUnityConnected = false;
unitySocket = null; // 소켓을 null로 설정
console.error('❌ Unity 연결 오류:', error);
console.log('🔍 Unity가 실행 중인지 확인하세요 (포트 10701)');
};
} catch (error) {
console.error('❌ Unity 연결 설정 오류:', error);
}
}
// 버튼 클릭 처리
function handleButtonClick(context, actionUUID) {
console.log('🎯 버튼 클릭 처리 시작');
console.log('📍 컨텍스트:', context);
console.log('📍 Action UUID:', actionUUID);
console.log('🔌 Unity 연결 상태:', isUnityConnected);
if (!isUnityConnected || !unitySocket) {
console.log('🔄 Unity 연결되지 않음, 재연결 시도...');
connectToUnity();
// 연결 후 잠시 대기한 후 다시 시도
setTimeout(() => {
if (isUnityConnected && unitySocket) {
handleButtonClick(context, actionUUID);
} else {
console.error('❌ Unity 재연결 실패');
}
}, 1000);
return;
}
// context별 settings 사용
const settings = getCurrentSettings(context);
let actionType = settings.actionType;
// actionType이 없으면 action UUID로 판단
if (!actionType && actionUUID) {
console.log('⚠️ actionType 없음! Action UUID로 결정:', actionUUID);
if (actionUUID === 'com.mirabox.streamingle.optitrack_marker_toggle') {
actionType = 'optitrack_marker_toggle';
console.log('✅ 마커 버튼으로 인식');
} else if (actionUUID === 'com.mirabox.streamingle.motion_recording_toggle') {
actionType = 'motion_recording_toggle';
console.log('🎬 모션 녹화 버튼으로 인식');
} else if (actionUUID === 'com.mirabox.streamingle.optitrack_reconnect') {
actionType = 'optitrack_reconnect';
console.log('🔄 OptiTrack 재접속 버튼으로 인식');
} else if (actionUUID === 'com.mirabox.streamingle.facial_motion_reconnect') {
actionType = 'facial_motion_reconnect';
console.log('😊 Facial Motion 재접속 버튼으로 인식');
} else if (actionUUID === 'com.mirabox.streamingle.screenshot') {
actionType = 'screenshot';
console.log('📸 스크린샷 버튼으로 인식');
} else if (actionUUID === 'com.mirabox.streamingle.screenshot_alpha') {
actionType = 'screenshot_alpha';
console.log('📷 알파 스크린샷 버튼으로 인식');
} else if (actionUUID === 'com.mirabox.streamingle.item') {
actionType = 'item';
} else if (actionUUID === 'com.mirabox.streamingle.event') {
actionType = 'event';
} else if (actionUUID === 'com.mirabox.streamingle.avatar_outfit') {
actionType = 'avatar_outfit';
} else {
actionType = 'camera';
}
// 설정에 저장
settings.actionType = actionType;
setCurrentSettings(context, settings);
}
if (!actionType) {
actionType = 'camera'; // 최종 폴백
}
console.log('╔════════════════════════════════════════╗');
console.log('║ BUTTON CLICKED - DEBUG INFO ║');
console.log('╠════════════════════════════════════════╣');
console.log(' Context:', context);
console.log(' Action UUID:', actionUUID);
console.log(' ActionType:', actionType);
console.log(' All Settings:', JSON.stringify(settings));
console.log('╚════════════════════════════════════════╝');
switch (actionType) {
case 'camera':
console.log('➡️ Routing to: CAMERA handler');
console.log(' Will send: {"type":"switch_camera","data":{...}}');
handleCameraAction(settings);
break;
case 'item':
console.log('➡️ Routing to: ITEM handler');
handleItemAction(settings);
break;
case 'event':
console.log('➡️ Routing to: EVENT handler');
handleEventAction(settings);
break;
case 'avatar_outfit':
console.log('➡️ Routing to: AVATAR OUTFIT handler');
handleAvatarOutfitAction(settings);
break;
case 'optitrack_marker_toggle':
console.log('➡️ Routing to: MARKER TOGGLE handler');
console.log(' Will send: {"type":"toggle_optitrack_markers"}');
handleOptitrackMarkerToggle(context);
break;
case 'motion_recording_toggle':
console.log('➡️ Routing to: MOTION RECORDING handler');
console.log(' Will send: {"type":"toggle_motion_recording"}');
handleMotionRecordingToggle(context);
break;
case 'optitrack_reconnect':
console.log('➡️ Routing to: OPTITRACK RECONNECT handler');
console.log(' Will send: {"type":"reconnect_optitrack"}');
handleOptitrackReconnect(context);
break;
case 'facial_motion_reconnect':
console.log('➡️ Routing to: FACIAL MOTION RECONNECT handler');
console.log(' Will send: {"type":"reconnect_facial_motion"}');
handleFacialMotionReconnect(context);
break;
case 'screenshot':
console.log('➡️ Routing to: SCREENSHOT handler');
console.log(' Will send: {"type":"capture_screenshot"}');
handleScreenshot(context);
break;
case 'screenshot_alpha':
console.log('➡️ Routing to: ALPHA SCREENSHOT handler');
console.log(' Will send: {"type":"capture_alpha_screenshot"}');
handleAlphaScreenshot(context);
break;
default:
console.log('⚠️ WARNING: Unknown actionType:', actionType);
console.log(' Defaulting to CAMERA handler');
handleCameraAction(settings);
}
}
// 카메라 액션 처리
function handleCameraAction(settings) {
let cameraIndex = settings.cameraIndex;
// 카메라 인덱스가 설정되지 않았으면 0 사용
if (typeof cameraIndex !== 'number') {
cameraIndex = 0;
console.log('⚠️ 카메라 인덱스가 설정되지 않음, 기본값 0 사용');
}
console.log('📹 전환할 카메라 인덱스:', cameraIndex);
// Unity에 카메라 전환 요청
const message = JSON.stringify({
type: 'switch_camera',
data: {
camera_index: cameraIndex
}
});
console.log('📤 Unity에 카메라 전환 요청 전송:', message);
console.log('🔍 Unity 연결 상태:', isUnityConnected);
console.log('🔍 Unity 소켓 상태:', !!unitySocket);
if (unitySocket && unitySocket.readyState === WebSocket.OPEN) {
unitySocket.send(message);
console.log('✅ 메시지 전송 완료');
} else {
console.error('❌ Unity 소켓이 연결되지 않음');
console.log('🔄 Unity 재연결 시도...');
connectToUnity();
}
}
// 아이템 액션 처리
function handleItemAction(settings) {
let itemIndex = settings.itemIndex;
const itemAction = settings.itemAction || 'toggle'; // 기본값은 toggle
// 아이템 인덱스가 설정되지 않았으면 0 사용
if (typeof itemIndex !== 'number') {
itemIndex = 0;
console.log('⚠️ 아이템 인덱스가 설정되지 않음, 기본값 0 사용');
}
console.log('🎯 아이템 액션:', itemAction, '인덱스:', itemIndex);
let messageType = 'toggle_item';
if (itemAction === 'set') {
messageType = 'set_item';
}
// Unity에 아이템 액션 요청
const message = JSON.stringify({
type: messageType,
data: {
item_index: itemIndex
}
});
console.log('📤 Unity에 아이템 액션 요청 전송:', message);
console.log('🔍 Unity 연결 상태:', isUnityConnected);
console.log('🔍 Unity 소켓 상태:', !!unitySocket);
if (unitySocket && unitySocket.readyState === WebSocket.OPEN) {
unitySocket.send(message);
console.log('✅ 메시지 전송 완료');
} else {
console.error('❌ Unity 소켓이 연결되지 않음');
console.log('🔄 Unity 재연결 시도...');
connectToUnity();
}
}
// 이벤트 액션 처리
function handleEventAction(settings) {
let eventIndex = settings.eventIndex;
const eventAction = settings.eventAction || 'execute'; // 기본값은 execute
// 이벤트 인덱스가 설정되지 않았으면 0 사용
if (typeof eventIndex !== 'number') {
eventIndex = 0;
console.log('⚠️ 이벤트 인덱스가 설정되지 않음, 기본값 0 사용');
}
console.log('🎯 이벤트 액션:', eventAction, '인덱스:', eventIndex);
let messageType = 'execute_event';
if (eventAction === 'set') {
messageType = 'set_event';
}
// Unity에 이벤트 액션 요청
const message = JSON.stringify({
type: messageType,
data: {
event_index: eventIndex
}
});
console.log('📤 Unity에 이벤트 액션 요청 전송:', message);
console.log('🔍 Unity 연결 상태:', isUnityConnected);
console.log('🔍 Unity 소켓 상태:', !!unitySocket);
if (unitySocket && unitySocket.readyState === WebSocket.OPEN) {
unitySocket.send(message);
console.log('✅ 메시지 전송 완료');
} else {
console.error('❌ Unity 소켓이 연결되지 않음');
console.log('🔄 Unity 재연결 시도...');
connectToUnity();
}
}
// 아바타 의상 액션 처리
function handleAvatarOutfitAction(settings) {
let avatarIndex = settings.avatarIndex;
let outfitIndex = settings.outfitIndex;
// 설정값들 검증
if (typeof avatarIndex !== 'number') {
avatarIndex = 0;
console.log('⚠️ 아바타 인덱스가 설정되지 않음, 기본값 0 사용');
}
if (typeof outfitIndex !== 'number') {
outfitIndex = 0;
console.log('⚠️ 의상 인덱스가 설정되지 않음, 기본값 0 사용');
}
console.log('👗 아바타 의상 전환:', { avatarIndex, outfitIndex });
// Unity에 아바타 의상 변경 요청
const message = JSON.stringify({
type: 'set_avatar_outfit',
data: {
avatar_index: avatarIndex,
outfit_index: outfitIndex
}
});
console.log('📤 Unity에 아바타 의상 변경 요청 전송:', message);
console.log('🔍 Unity 연결 상태:', isUnityConnected);
console.log('🔍 Unity 소켓 상태:', !!unitySocket);
if (unitySocket && unitySocket.readyState === WebSocket.OPEN) {
unitySocket.send(message);
console.log('✅ 메시지 전송 완료');
} else {
console.error('❌ Unity 소켓이 연결되지 않음');
console.log('🔄 Unity 재연결 시도...');
connectToUnity();
}
}
// OptiTrack 마커 토글 액션 처리
function handleOptitrackMarkerToggle(context) {
console.log('🎯 OptiTrack 마커 토글 실행');
// Unity에 마커 토글 요청
const message = JSON.stringify({
type: 'toggle_optitrack_markers'
});
console.log('📤 Unity에 OptiTrack 마커 토글 요청 전송:', message);
console.log('🔍 Unity 연결 상태:', isUnityConnected);
console.log('🔍 Unity 소켓 상태:', !!unitySocket);
if (unitySocket && unitySocket.readyState === WebSocket.OPEN) {
unitySocket.send(message);
console.log('✅ 메시지 전송 완료');
// 버튼 상태 토글 (0 <-> 1)
toggleButtonState(context);
} else {
console.error('❌ Unity 소켓이 연결되지 않음');
console.log('🔄 Unity 재연결 시도...');
connectToUnity();
}
}
// 버튼 상태 토글 함수
function toggleButtonState(context) {
const settings = getCurrentSettings(context);
const currentState = settings.currentState || 0;
const newState = currentState === 0 ? 1 : 0;
// 설정 업데이트
settings.currentState = newState;
setCurrentSettings(context, settings);
// 버튼 상태 변경
const stateMessage = {
event: 'setState',
context: context,
payload: {
state: newState,
target: 0 // hardware and software
}
};
if (websocket) {
websocket.send(JSON.stringify(stateMessage));
console.log('🎨 버튼 상태 업데이트:', newState === 0 ? 'ON' : 'OFF');
}
// 제목도 함께 변경
const title = newState === 0 ? '마커\nON' : '마커\nOFF';
setButtonTitle(context, title);
}
// 모션 녹화 토글 액션 처리
function handleMotionRecordingToggle(context) {
console.log('🎬 모션 녹화 토글 실행');
// Unity에 모션 녹화 토글 요청
const message = JSON.stringify({
type: 'toggle_motion_recording'
});
console.log('📤 Unity에 모션 녹화 토글 요청 전송:', message);
console.log('🔍 Unity 연결 상태:', isUnityConnected);
console.log('🔍 Unity 소켓 상태:', !!unitySocket);
if (unitySocket && unitySocket.readyState === WebSocket.OPEN) {
unitySocket.send(message);
console.log('✅ 메시지 전송 완료');
// 버튼 상태 토글 (0 <-> 1)
toggleMotionRecordingButtonState(context);
} else {
console.error('❌ Unity 소켓이 연결되지 않음');
console.log('🔄 Unity 재연결 시도...');
connectToUnity();
}
}
// 모션 녹화 버튼 상태 토글 함수
function toggleMotionRecordingButtonState(context) {
const settings = getCurrentSettings(context);
const currentState = settings.currentState || 0;
const newState = currentState === 0 ? 1 : 0;
// 설정 업데이트
settings.currentState = newState;
setCurrentSettings(context, settings);
// 버튼 상태 변경
const stateMessage = {
event: 'setState',
context: context,
payload: {
state: newState,
target: 0 // hardware and software
}
};
if (websocket) {
websocket.send(JSON.stringify(stateMessage));
console.log('🎨 녹화 버튼 상태 업데이트:', newState === 0 ? 'OFF' : 'REC');
}
// 제목도 함께 변경
const title = newState === 0 ? '녹화\nOFF' : '녹화\nREC';
setButtonTitle(context, title);
}
// OptiTrack 재접속 액션 처리
function handleOptitrackReconnect(context) {
console.log('🔄 OptiTrack 재접속 실행');
// Unity에 OptiTrack 재접속 요청
const message = JSON.stringify({
type: 'reconnect_optitrack'
});
console.log('📤 Unity에 OptiTrack 재접속 요청 전송:', message);
console.log('🔍 Unity 연결 상태:', isUnityConnected);
console.log('🔍 Unity 소켓 상태:', !!unitySocket);
if (unitySocket && unitySocket.readyState === WebSocket.OPEN) {
unitySocket.send(message);
console.log('✅ 메시지 전송 완료');
// 재접속은 상태가 없는 단순 액션이므로 버튼 상태 변경 없음
// 피드백을 위해 제목을 잠시 변경할 수 있음
setButtonTitle(context, '재접속\n중...');
// 1초 후 원래 제목으로 복구
setTimeout(() => {
setButtonTitle(context, '옵티트랙\n재접속');
}, 1000);
} else {
console.error('❌ Unity 소켓이 연결되지 않음');
console.log('🔄 Unity 재연결 시도...');
connectToUnity();
}
}
// Facial Motion 재접속 액션 처리
function handleFacialMotionReconnect(context) {
console.log('😊 Facial Motion 재접속 실행');
// Unity에 Facial Motion 재접속 요청
const message = JSON.stringify({
type: 'reconnect_facial_motion'
});
console.log('📤 Unity에 Facial Motion 재접속 요청 전송:', message);
console.log('🔍 Unity 연결 상태:', isUnityConnected);
console.log('🔍 Unity 소켓 상태:', !!unitySocket);
if (unitySocket && unitySocket.readyState === WebSocket.OPEN) {
unitySocket.send(message);
console.log('✅ 메시지 전송 완료');
// 재접속은 상태가 없는 단순 액션이므로 버튼 상태 변경 없음
// 피드백을 위해 제목을 잠시 변경할 수 있음
setButtonTitle(context, '재접속\n중...');
// 1초 후 원래 제목으로 복구
setTimeout(() => {
setButtonTitle(context, '페이셜\n재접속');
}, 1000);
} else {
console.error('❌ Unity 소켓이 연결되지 않음');
console.log('🔄 Unity 재연결 시도...');
connectToUnity();
}
}
// 스크린샷 액션 처리
function handleScreenshot(context) {
console.log('📸 스크린샷 촬영 실행');
// Unity에 스크린샷 요청
const message = JSON.stringify({
type: 'capture_screenshot'
});
console.log('📤 Unity에 스크린샷 요청 전송:', message);
console.log('🔍 Unity 연결 상태:', isUnityConnected);
console.log('🔍 Unity 소켓 상태:', !!unitySocket);
if (unitySocket && unitySocket.readyState === WebSocket.OPEN) {
unitySocket.send(message);
console.log('✅ 메시지 전송 완료');
// 피드백을 위해 제목을 잠시 변경
setButtonTitle(context, '촬영\n중...');
// 1초 후 원래 제목으로 복구
setTimeout(() => {
setButtonTitle(context, '스크린샷');
}, 1000);
} else {
console.error('❌ Unity 소켓이 연결되지 않음');
console.log('🔄 Unity 재연결 시도...');
connectToUnity();
}
}
// 알파 스크린샷 액션 처리
function handleAlphaScreenshot(context) {
console.log('📷 알파 스크린샷 촬영 실행');
// Unity에 알파 스크린샷 요청
const message = JSON.stringify({
type: 'capture_alpha_screenshot'
});
console.log('📤 Unity에 알파 스크린샷 요청 전송:', message);
console.log('🔍 Unity 연결 상태:', isUnityConnected);
console.log('🔍 Unity 소켓 상태:', !!unitySocket);
if (unitySocket && unitySocket.readyState === WebSocket.OPEN) {
unitySocket.send(message);
console.log('✅ 메시지 전송 완료');
// 피드백을 위해 제목을 잠시 변경
setButtonTitle(context, '촬영\n중...');
// 1초 후 원래 제목으로 복구
setTimeout(() => {
setButtonTitle(context, '알파\n스크린샷');
}, 1000);
} else {
console.error('❌ Unity 소켓이 연결되지 않음');
console.log('🔄 Unity 재연결 시도...');
connectToUnity();
}
}
// Property Inspector 메시지 처리
function handlePropertyInspectorMessage(payload, context, actionUUID) {
const command = payload.command;
console.log('📤 Property Inspector 명령 처리 시작');
console.log(' - payload:', payload);
console.log(' - context:', context);
console.log(' - actionUUID:', actionUUID);
console.log(' - command:', command);
switch (command) {
case 'get_unity_status':
console.log('📡 Unity 상태 요청 - context:', context, 'action:', actionUUID);
console.log('📡 Unity 연결 상태:', isUnityConnected);
console.log('📡 카메라 목록 길이:', cameraList.length);
console.log('📡 아이템 목록 길이:', itemList.length);
console.log('📡 아바타 목록 길이:', avatarOutfitList.length);
sendToPropertyInspector(context, 'unity_connected', { connected: isUnityConnected }, actionUUID);
// Unity 연결된 상태라면 무조건 아바타 목록 요청
if (isUnityConnected && unitySocket && unitySocket.readyState === WebSocket.OPEN) {
console.log('📡 Unity 연결됨 - 모든 데이터 요청 시작');
// 아바타 목록이 비어있으면 무조건 요청
if (avatarOutfitList.length === 0) {
console.log('📡 아바타 목록 비어있음 - Unity에 요청');
const avatarMessage = JSON.stringify({ type: 'get_avatar_outfit_list' });
unitySocket.send(avatarMessage);
console.log('📤 Unity에 아바타 의상 목록 강제 요청 전송');
}
}
if (isUnityConnected) {
// actionUUID에 따라 해당하는 데이터만 전송
console.log('📡 ActionUUID에 따른 데이터 전송:', actionUUID);
if (actionUUID === 'com.mirabox.streamingle.camera') {
if (cameraList.length > 0) {
// 현재 활성 카메라 인덱스 찾기
const currentCameraIndex = cameraList.findIndex(cam => cam.isActive) || 0;
console.log('📡 현재 활성 카메라 인덱스:', currentCameraIndex);
sendToPropertyInspector(context, 'camera_list', {
cameras: cameraList,
currentIndex: currentCameraIndex
}, actionUUID);
}
} else if (actionUUID === 'com.mirabox.streamingle.item') {
if (itemList.length > 0) {
// 현재 활성 아이템 인덱스 찾기
const currentItemIndex = itemList.findIndex(item => item.isActive) || 0;
console.log('📡 현재 활성 아이템 인덱스:', currentItemIndex);
sendToPropertyInspector(context, 'item_list', {
items: itemList,
currentIndex: currentItemIndex
}, actionUUID);
}
} else if (actionUUID === 'com.mirabox.streamingle.event') {
if (eventList.length > 0) {
// 현재 활성 이벤트 인덱스 찾기
const currentEventIndex = eventList.findIndex(event => event.isActive) || 0;
console.log('📡 현재 활성 이벤트 인덱스:', currentEventIndex);
sendToPropertyInspector(context, 'event_list', {
events: eventList,
currentIndex: currentEventIndex
}, actionUUID);
}
} else if (actionUUID === 'com.mirabox.streamingle.avatar_outfit') {
if (avatarOutfitList.length > 0) {
// 현재 활성 아바타 인덱스 찾기
const currentAvatarIndex = avatarOutfitList.findIndex(avatar => avatar.current_outfit_index >= 0) || 0;
console.log('📡 현재 아바타 목록:', avatarOutfitList.length, '개');
console.log('📡 현재 활성 아바타 인덱스:', currentAvatarIndex);
sendToPropertyInspector(context, 'avatar_outfit_list', {
avatars: avatarOutfitList,
currentIndex: currentAvatarIndex
}, actionUUID);
} else {
console.log('📡 아바타 목록이 비어있음, Unity에 요청');
// 아바타 목록이 없으면 Unity에 요청
if (unitySocket && unitySocket.readyState === WebSocket.OPEN) {
const message = JSON.stringify({ type: 'get_avatar_outfit_list' });
unitySocket.send(message);
console.log('📤 Unity에 아바타 의상 목록 자동 요청 전송');
}
}
}
} else {
// Unity가 연결되지 않은 경우에도 해당 컨트롤러에 맞는 빈 목록만 전송
console.log('📡 Unity 연결 안됨 - ActionUUID에 따른 빈 목록 전송:', actionUUID);
if (actionUUID === 'com.mirabox.streamingle.camera') {
sendToPropertyInspector(context, 'camera_list', {
cameras: [],
currentIndex: 0
}, actionUUID);
} else if (actionUUID === 'com.mirabox.streamingle.item') {
sendToPropertyInspector(context, 'item_list', {
items: [],
currentIndex: 0
}, actionUUID);
} else if (actionUUID === 'com.mirabox.streamingle.event') {
sendToPropertyInspector(context, 'event_list', {
events: [],
currentIndex: 0
}, actionUUID);
} else if (actionUUID === 'com.mirabox.streamingle.avatar_outfit') {
sendToPropertyInspector(context, 'avatar_outfit_list', {
avatars: [],
currentIndex: 0
}, actionUUID);
}
}
break;
case 'get_camera_list':
console.log('📹 카메라 목록 요청');
if (isUnityConnected && unitySocket) {
const message = JSON.stringify({ type: 'get_camera_list' });
unitySocket.send(message);
console.log('📤 Unity에 카메라 목록 요청 전송');
}
break;
case 'get_item_list':
console.log('🎯 아이템 목록 요청');
if (isUnityConnected && unitySocket) {
const message = JSON.stringify({ type: 'get_item_list' });
unitySocket.send(message);
console.log('📤 Unity에 아이템 목록 요청 전송');
}
break;
case 'get_event_list':
console.log('🎯 이벤트 목록 요청');
if (isUnityConnected && unitySocket) {
const message = JSON.stringify({ type: 'get_event_list' });
unitySocket.send(message);
console.log('📤 Unity에 이벤트 목록 요청 전송');
}
break;
case 'switch_camera':
console.log('📹 카메라 전환 요청:', payload.cameraIndex);
if (isUnityConnected && unitySocket) {
const message = JSON.stringify({
type: 'switch_camera',
data: { camera_index: payload.cameraIndex }
});
unitySocket.send(message);
console.log('📤 Unity에 카메라 전환 요청 전송');
}
break;
case 'toggle_item':
console.log('🎯 아이템 토글 요청:', payload.itemIndex);
if (isUnityConnected && unitySocket) {
const message = JSON.stringify({
type: 'toggle_item',
data: { item_index: payload.itemIndex }
});
unitySocket.send(message);
console.log('📤 Unity에 아이템 토글 요청 전송');
}
break;
case 'set_item':
console.log('🎯 아이템 설정 요청:', payload.itemIndex);
if (isUnityConnected && unitySocket) {
const message = JSON.stringify({
type: 'set_item',
data: { item_index: payload.itemIndex }
});
unitySocket.send(message);
console.log('📤 Unity에 아이템 설정 요청 전송');
}
break;
case 'execute_event':
console.log('🎯 이벤트 실행 요청:', payload.eventIndex);
if (isUnityConnected && unitySocket) {
const message = JSON.stringify({
type: 'execute_event',
data: { event_index: payload.eventIndex }
});
unitySocket.send(message);
console.log('📤 Unity에 이벤트 실행 요청 전송');
}
break;
case 'set_event':
console.log('🎯 이벤트 설정 요청:', payload.eventIndex);
if (isUnityConnected && unitySocket) {
const message = JSON.stringify({
type: 'set_event',
data: { event_index: payload.eventIndex }
});
unitySocket.send(message);
console.log('📤 Unity에 이벤트 설정 요청 전송');
}
break;
case 'refresh_camera_list':
console.log('🔄 카메라 목록 새로고침 요청');
if (isUnityConnected && unitySocket) {
const message = JSON.stringify({ type: 'get_camera_list' });
unitySocket.send(message);
console.log('📤 Unity에 카메라 목록 요청 전송');
}
break;
case 'refresh_item_list':
console.log('🔄 아이템 목록 새로고침 요청');
if (isUnityConnected && unitySocket) {
const message = JSON.stringify({ type: 'get_item_list' });
unitySocket.send(message);
console.log('📤 Unity에 아이템 목록 요청 전송');
}
break;
case 'refresh_event_list':
console.log('🔄 이벤트 목록 새로고침 요청');
if (isUnityConnected && unitySocket) {
const message = JSON.stringify({ type: 'get_event_list' });
unitySocket.send(message);
console.log('📤 Unity에 이벤트 목록 요청 전송');
}
break;
case 'refresh_avatar_outfit_list':
console.log('🔄 아바타 의상 목록 새로고침 요청');
if (!isUnityConnected || !unitySocket) {
console.log('🔄 Unity 연결되지 않음, 재연결 시도...');
connectToUnity();
} else {
const message = JSON.stringify({ type: 'get_avatar_outfit_list' });
if (unitySocket && unitySocket.readyState === WebSocket.OPEN) {
unitySocket.send(message);
console.log('📤 Unity에 아바타 의상 목록 요청 전송');
} else {
console.error('❌ Unity 소켓이 연결되지 않음');
console.log('🔄 Unity 재연결 시도...');
connectToUnity();
}
}
break;
case 'set_avatar_outfit':
console.log('👗 아바타 의상 설정 요청:', payload);
if (isUnityConnected && unitySocket) {
const message = JSON.stringify({
type: 'set_avatar_outfit',
data: {
avatar_index: payload.avatarIndex,
outfit_index: payload.outfitIndex
}
});
unitySocket.send(message);
console.log('📤 Unity에 아바타 의상 설정 요청 전송');
}
break;
case 'update_title':
console.log('🏷️ 버튼 제목 업데이트 요청');
console.log('🏷️ 요청 payload:', payload);
console.log('🏷️ 현재 Plugin의 avatarOutfitList:', avatarOutfitList);
updateButtonTitle(context);
break;
case 'debug_test':
console.log('🔍 디버그 테스트 메시지 수신!');
console.log(' - context:', context);
console.log(' - actionUUID:', actionUUID);
console.log(' - payload:', payload);
console.log(' - 현재 등록된 컨텍스트들:', Array.from(buttonContexts.keys()));
console.log(' - 이 컨텍스트의 설정:', getCurrentSettings(context));
// 응답을 보내서 Property Inspector가 받을 수 있는지 확인
sendToPropertyInspector(context, 'debug_response', {
message: 'Plugin received debug test',
receivedContext: context,
registeredContexts: Array.from(buttonContexts.keys())
}, actionUUID);
break;
default:
console.log('❓ 알 수 없는 Property Inspector 명령:', command);
}
}
// Property Inspector로 메시지 전송 (action UUID 지정 가능)
function sendToPropertyInspector(context, event, payload, actionUUID = null) {
console.log('📤 Property Inspector로 메시지 전송 시작 - context:', context, 'event:', event, 'actionUUID:', actionUUID);
if (!websocket || !context) {
console.log('🚫 WebSocket 또는 context 없음 - Property Inspector 메시지 전송 건너뜀');
console.log('🔍 websocket 상태:', !!websocket);
console.log('🔍 context:', context);
return;
}
// action UUID 결정
let action = actionUUID;
if (!action) {
// Property Inspector에서 보내는 메시지의 action UUID를 그대로 사용
// 카메라 컨트롤러 Property Inspector는 'com.mirabox.streamingle.camera'를 사용
// 아이템 컨트롤러 Property Inspector는 'com.mirabox.streamingle.item'을 사용
action = 'com.mirabox.streamingle.camera'; // 기본값
// context별로 적절한 action 결정
const settings = getCurrentSettings(context);
console.log('📤 context 설정:', settings);
const actionType = settings.actionType || 'camera';
if (actionType === 'item') {
action = 'com.mirabox.streamingle.item';
} else if (actionType === 'event') {
action = 'com.mirabox.streamingle.event';
} else if (actionType === 'avatar_outfit') {
action = 'com.mirabox.streamingle.avatar_outfit';
}
}
console.log('📤 결정된 action:', action);
const message = {
action: action,
event: 'sendToPropertyInspector',
context: context,
payload: {
event: event,
...payload
}
};
console.log('📤 Property Inspector로 전송할 메시지:', JSON.stringify(message, null, 2));
websocket.send(JSON.stringify(message));
console.log('✅ Property Inspector로 메시지 전송 완료:', event, payload);
}
// Unity 메시지 처리
function handleUnityMessage(data) {
console.log('📨 Unity 메시지 수신:', data.type);
console.log('📋 전체 메시지 데이터:', JSON.stringify(data, null, 2));
switch (data.type) {
case 'connection_established':
console.log('🎉 Unity 연결 확인됨');
console.log('📋 connection_established 데이터:', JSON.stringify(data, null, 2));
// 연결 시 카메라 및 아이템 데이터 저장
if (data.data) {
// 카메라 데이터 처리
if (data.data.camera_data) {
let cameras = data.data.camera_data.presets || data.data.camera_data;
if (Array.isArray(cameras)) {
cameraList = cameras;
console.log('📹 카메라 목록 저장됨:', cameraList.length, '개');
} else {
cameraList = [];
console.log('⚠️ 카메라 데이터가 배열이 아님:', cameras);
}
}
// 아이템 데이터 처리
if (data.data.item_data) {
let items = data.data.item_data.items || data.data.item_data;
if (Array.isArray(items)) {
itemList = items;
console.log('🎯 아이템 목록 저장됨:', itemList.length, '개');
} else {
itemList = [];
console.log('⚠️ 아이템 데이터가 배열이 아님:', items);
}
}
// 이벤트 데이터 처리
if (data.data.event_data) {
let events = data.data.event_data.events || data.data.event_data;
if (Array.isArray(events)) {
eventList = events;
console.log('🎯 이벤트 목록 저장됨:', eventList.length, '개');
} else {
eventList = [];
console.log('⚠️ 이벤트 데이터가 배열이 아님:', events);
}
}
// 아바타 의상 데이터 처리
if (data.data.avatar_outfit_data) {
let avatars = data.data.avatar_outfit_data.avatars || data.data.avatar_outfit_data;
if (Array.isArray(avatars)) {
avatarOutfitList = avatars;
console.log('👗 아바타 의상 목록 저장됨:', avatarOutfitList.length, '개');
} else {
avatarOutfitList = [];
console.log('⚠️ 아바타 의상 데이터가 배열이 아님:', avatars);
}
} else {
// 아바타 의상 데이터가 없어도 빈 배열로 초기화
console.log('⚠️ 아바타 의상 데이터가 없음, 빈 배열로 초기화');
avatarOutfitList = [];
}
updateAllButtonTitles();
// Property Inspector들에게 Unity 연결 상태 알림
console.log('📤 Property Inspector들에게 Unity 연결 알림 전송 시작');
console.log('📤 현재 등록된 컨텍스트들:', Array.from(buttonContexts.keys()));
if (buttonContexts.size === 0) {
console.log('⚠️ 등록된 컨텍스트가 없음 - Property Inspector가 아직 연결되지 않았을 수 있음');
} else {
console.log('📤 등록된 컨텍스트들:', Array.from(buttonContexts.keys()));
for (const context of buttonContexts.keys()) {
console.log('📤 Property Inspector로 전송할 컨텍스트:', context);
// context별로 적절한 action UUID 결정
const settings = getCurrentSettings(context);
const actionType = settings.actionType || 'camera';
let actionUUID = 'com.mirabox.streamingle.camera';
if (actionType === 'item') {
actionUUID = 'com.mirabox.streamingle.item';
} else if (actionType === 'event') {
actionUUID = 'com.mirabox.streamingle.event';
} else if (actionType === 'avatar_outfit') {
actionUUID = 'com.mirabox.streamingle.avatar_outfit';
}
console.log('🔍 컨텍스트 분석:', context, 'Action Type:', actionType, 'Action UUID:', actionUUID);
sendToPropertyInspector(context, 'unity_connected', { connected: true }, actionUUID);
// action UUID에 따라 적절한 데이터만 전송
if (actionUUID === 'com.mirabox.streamingle.camera') {
// 카메라 컨트롤러에는 카메라 데이터만 전송
let currentCameraIndex = 0;
if (typeof data.data.camera_data?.current_index === 'number' && data.data.camera_data.current_index >= 0) {
currentCameraIndex = data.data.camera_data.current_index;
}
console.log('📹 카메라 컨트롤러에 카메라 데이터 전송:', context, '카메라 수:', cameraList.length);
sendToPropertyInspector(context, 'camera_list', {
cameras: cameraList,
currentIndex: currentCameraIndex
}, actionUUID);
} else if (actionUUID === 'com.mirabox.streamingle.item') {
// 아이템 컨트롤러에는 아이템 데이터만 전송
let currentItemIndex = 0;
if (typeof data.data.item_data?.current_index === 'number' && data.data.item_data.current_index >= 0) {
currentItemIndex = data.data.item_data.current_index;
}
console.log('🎯 아이템 컨트롤러에 아이템 데이터 전송:', context, '아이템 수:', itemList.length);
sendToPropertyInspector(context, 'item_list', {
items: itemList,
currentIndex: currentItemIndex
}, actionUUID);
} else if (actionUUID === 'com.mirabox.streamingle.event') {
// 이벤트 컨트롤러에는 이벤트 데이터만 전송
let currentEventIndex = 0;
if (typeof data.data.event_data?.current_index === 'number' && data.data.event_data.current_index >= 0) {
currentEventIndex = data.data.event_data.current_index;
}
console.log('🎯 이벤트 컨트롤러에 이벤트 데이터 전송:', context, '이벤트 수:', eventList.length);
sendToPropertyInspector(context, 'event_list', {
events: eventList,
currentIndex: currentEventIndex
}, actionUUID);
} else if (actionUUID === 'com.mirabox.streamingle.avatar_outfit') {
// 아바타 의상 컨트롤러에는 아바타 의상 데이터만 전송
let currentAvatarIndex = 0;
if (typeof data.data.avatar_outfit_data?.current_avatar_index === 'number' && data.data.avatar_outfit_data.current_avatar_index >= 0) {
currentAvatarIndex = data.data.avatar_outfit_data.current_avatar_index;
}
console.log('👗 아바타 의상 컨트롤러에 아바타 데이터 전송:', context, '아바타 수:', avatarOutfitList.length);
// 아바타 목록이 비어있더라도 연결 상태와 함께 전송
sendToPropertyInspector(context, 'avatar_outfit_list', {
avatars: avatarOutfitList || [],
currentIndex: currentAvatarIndex
}, actionUUID);
}
}
console.log('✅ Property Inspector들에게 Unity 연결 알림 전송 완료');
}
} else {
console.log('⚠️ Unity 연결 시 데이터가 없음');
console.log('📋 data.data:', data.data);
}
break;
case 'camera_list_response':
console.log('📹 카메라 목록 수신');
if (data.data && data.data.camera_data) {
let cameras = data.data.camera_data.presets || data.data.camera_data;
if (Array.isArray(cameras)) {
cameraList = cameras;
const currentIndex = data.data.current_camera ?? data.data.camera_data?.current_index ?? 0;
console.log('📹 카메라 목록 업데이트됨:', cameraList.length, '개');
updateAllButtonTitles();
// Property Inspector들에게 카메라 목록 전송 (카메라 컨트롤러만)
for (const context of buttonContexts.keys()) {
const settings = getCurrentSettings(context);
const actionType = settings.actionType || 'camera';
if (actionType === 'camera') {
sendToPropertyInspector(context, 'camera_list', {
cameras: cameraList,
currentIndex: currentIndex
}, 'com.mirabox.streamingle.camera');
// 카메라 상태에 따라 버튼 상태도 업데이트
const cameraIndex = settings.cameraIndex || 0;
const isActive = (cameraIndex === currentIndex);
setButtonState(context, isActive);
console.log('🎨 카메라 변경으로 상태 업데이트:', context, '카메라 인덱스:', cameraIndex, '활성:', isActive);
}
}
} else {
console.log('⚠️ 카메라 목록 응답에서 카메라 데이터가 배열이 아님');
console.log('📋 cameras:', cameras);
}
}
break;
case 'camera_changed':
console.log('📹 카메라 변경 알림');
if (data.data && data.data.camera_data) {
let cameras = data.data.camera_data.presets || data.data.camera_data;
if (Array.isArray(cameras)) {
cameraList = cameras;
console.log('📹 카메라 목록 업데이트됨:', cameraList.length, '개');
updateAllButtonTitles();
// Property Inspector들에게 카메라 목록 전송 (카메라 컨트롤러만)
for (const context of buttonContexts.keys()) {
const settings = getCurrentSettings(context);
const actionType = settings.actionType || 'camera';
if (actionType === 'camera') {
sendToPropertyInspector(context, 'camera_list', {
cameras: cameraList,
currentIndex: data.data.camera_data?.current_index || 0
}, 'com.mirabox.streamingle.camera');
// 카메라 상태에 따라 버튼 상태도 업데이트
const cameraIndex = settings.cameraIndex || 0;
let isActive = false;
if (cameraList && cameraList[cameraIndex]) {
isActive = cameraList[cameraIndex].isActive === true;
}
setButtonState(context, isActive);
console.log('🎨 카메라 변경으로 상태 업데이트:', context, '카메라 인덱스:', cameraIndex, 'isActive:', isActive);
}
}
} else {
console.log('⚠️ 카메라 변경 응답에서 카메라 데이터가 배열이 아님');
console.log('📋 cameras:', cameras);
}
}
break;
case 'item_changed':
console.log('🎯 아이템 변경 알림');
if (data.data && data.data.item_data) {
let items = data.data.item_data.items || data.data.item_data;
if (Array.isArray(items)) {
itemList = items;
console.log('🎯 아이템 목록 업데이트됨:', itemList.length, '개');
updateAllButtonTitles();
// Property Inspector들에게 아이템 목록 전송 (아이템 컨트롤러만)
for (const context of buttonContexts.keys()) {
const settings = getCurrentSettings(context);
const actionType = settings.actionType || 'camera';
if (actionType === 'item') {
sendToPropertyInspector(context, 'item_list', {
items: itemList,
currentIndex: data.data.item_data?.current_index || 0
}, 'com.mirabox.streamingle.item');
// 아이템 상태에 따라 버튼 상태도 업데이트
const itemIndex = settings.itemIndex || 0;
if (itemList[itemIndex]) {
const isActive = itemList[itemIndex].isActive !== false; // 기본값은 true
setButtonState(context, isActive);
console.log('🎨 아이템 변경으로 상태 업데이트:', context, '아이템 인덱스:', itemIndex, '활성:', isActive);
}
}
}
} else {
console.log('⚠️ 아이템 목록 응답에서 아이템 데이터가 배열이 아님');
console.log('📋 items:', items);
}
}
break;
case 'item_list_response':
console.log('🎯 아이템 목록 수신');
if (data.data && data.data.item_data) {
let items = data.data.item_data.items || data.data.item_data;
if (Array.isArray(items)) {
itemList = items;
const currentIndex = data.data.current_item ?? data.data.item_data?.current_index ?? 0;
console.log('🎯 아이템 목록 업데이트됨:', itemList.length, '개');
updateAllButtonTitles();
// Property Inspector들에게 아이템 목록 전송 (아이템 컨트롤러만)
for (const context of buttonContexts.keys()) {
const settings = getCurrentSettings(context);
const actionType = settings.actionType || 'camera';
if (actionType === 'item') {
sendToPropertyInspector(context, 'item_list', {
items: itemList,
currentIndex: currentIndex
}, 'com.mirabox.streamingle.item');
// 아이템 상태에 따라 버튼 상태도 업데이트
const itemIndex = settings.itemIndex || 0;
if (itemList[itemIndex]) {
const isActive = itemList[itemIndex].isActive !== false; // 기본값은 true
setButtonState(context, isActive);
}
}
}
} else {
console.log('⚠️ 아이템 목록 응답에서 아이템 데이터가 배열이 아님');
console.log('📋 items:', items);
}
}
break;
case 'event_changed':
console.log('🎯 이벤트 변경 알림');
console.log('📋 이벤트 데이터 구조:', JSON.stringify(data.data, null, 2));
if (data.data && data.data.event_data) {
let events = data.data.event_data.events || data.data.event_data;
console.log('🎯 추출된 이벤트 배열:', events);
console.log('🎯 이벤트 배열 타입:', Array.isArray(events) ? 'Array' : typeof events);
if (Array.isArray(events)) {
eventList = events;
console.log('🎯 이벤트 목록 업데이트됨:', eventList.length, '개');
console.log('🎯 이벤트 목록 내용:', JSON.stringify(eventList, null, 2));
updateAllButtonTitles();
// Property Inspector들에게 이벤트 목록 전송 (이벤트 컨트롤러만)
for (const context of buttonContexts.keys()) {
const settings = getCurrentSettings(context);
const actionType = settings.actionType || 'camera';
console.log('🔍 컨텍스트 분석:', context, 'Action Type:', actionType);
if (actionType === 'event') {
console.log('🎯 이벤트 컨트롤러 발견 - Property Inspector에 데이터 전송');
sendToPropertyInspector(context, 'event_list', {
events: eventList,
currentIndex: data.data.event_data?.current_index || 0
}, 'com.mirabox.streamingle.event');
} else {
console.log('⚠️ 이벤트 컨트롤러가 아님 - Action Type:', actionType);
}
}
} else {
console.log('⚠️ 이벤트 목록 응답에서 이벤트 데이터가 배열이 아님');
console.log('📋 events:', events);
}
}
break;
case 'event_list_response':
console.log('🎯 이벤트 목록 수신');
if (data.data && data.data.event_data) {
let events = data.data.event_data.events || data.data.event_data;
if (Array.isArray(events)) {
eventList = events;
const currentIndex = data.data.current_event ?? data.data.event_data?.current_index ?? 0;
console.log('🎯 이벤트 목록 업데이트됨:', eventList.length, '개');
updateAllButtonTitles();
// Property Inspector들에게 이벤트 목록 전송 (이벤트 컨트롤러만)
for (const context of buttonContexts.keys()) {
const settings = getCurrentSettings(context);
const actionType = settings.actionType || 'camera';
if (actionType === 'event') {
sendToPropertyInspector(context, 'event_list', {
events: eventList,
currentIndex: currentIndex
}, 'com.mirabox.streamingle.event');
}
}
} else {
console.log('⚠️ 이벤트 목록 응답에서 이벤트 데이터가 배열이 아님');
console.log('📋 events:', events);
}
}
break;
case 'avatar_outfit_changed':
console.log('👗 아바타 의상 변경 알림');
if (data.data && data.data.avatar_outfit_data) {
let avatars = data.data.avatar_outfit_data.avatars || data.data.avatar_outfit_data;
if (Array.isArray(avatars)) {
avatarOutfitList = avatars;
console.log('👗 아바타 의상 목록 업데이트됨:', avatarOutfitList.length, '개');
updateAllButtonTitles();
// Property Inspector들에게 아바타 의상 목록 전송 (아바타 의상 컨트롤러만)
for (const context of buttonContexts.keys()) {
const settings = getCurrentSettings(context);
const actionType = settings.actionType || 'camera';
if (actionType === 'avatar_outfit') {
sendToPropertyInspector(context, 'avatar_outfit_changed', {
avatars: avatarOutfitList,
currentIndex: data.data.avatar_outfit_data?.current_avatar_index || 0
}, 'com.mirabox.streamingle.avatar_outfit');
}
}
} else {
console.log('⚠️ 아바타 의상 변경 응답에서 아바타 데이터가 배열이 아님');
console.log('📋 avatars:', avatars);
}
}
break;
case 'avatar_outfit_list_response':
console.log('👗 아바타 의상 목록 응답');
if (data.data && data.data.avatar_outfit_data) {
let avatars = data.data.avatar_outfit_data.avatars || data.data.avatar_outfit_data;
let currentIndex = data.data.avatar_outfit_data?.current_avatar_index || 0;
if (Array.isArray(avatars)) {
avatarOutfitList = avatars;
console.log('👗 아바타 의상 목록 응답 처리됨:', avatarOutfitList.length, '개');
// 아바타 데이터 상세 로그
avatarOutfitList.forEach((avatar, index) => {
console.log(`👗 아바타 ${index}:`, {
name: avatar.name,
nameType: typeof avatar.name,
current_outfit_name: avatar.current_outfit_name,
outfits: avatar.outfits ? avatar.outfits.length : 'no outfits'
});
});
updateAllButtonTitles();
// Property Inspector들에게 아바타 의상 목록 전송
for (const context of buttonContexts.keys()) {
const settings = getCurrentSettings(context);
const actionType = settings.actionType || 'camera';
if (actionType === 'avatar_outfit') {
sendToPropertyInspector(context, 'avatar_outfit_list', {
avatars: avatarOutfitList,
currentIndex: currentIndex
}, 'com.mirabox.streamingle.avatar_outfit');
}
}
} else {
console.log('⚠️ 아바타 의상 목록 응답에서 아바타 데이터가 배열이 아님');
console.log('📋 avatars:', avatars);
}
}
break;
default:
console.log('❓ 알 수 없는 Unity 메시지 타입:', data.type);
}
}
// 버튼 제목을 직접 설정하는 함수
function setButtonTitle(context, title) {
if (!websocket || !context) {
console.log('🚫 WebSocket 또는 context 없음 - 제목 설정 건너뜀');
return;
}
console.log(`🏷️ 버튼 제목 설정: "${title}" (Context: ${context})`);
const message = {
event: 'setTitle',
context: context,
payload: {
title: title,
target: 0 // hardware and software
}
};
websocket.send(JSON.stringify(message));
console.log('✅ setTitle 메시지 전송 완료');
}
// 모든 버튼의 제목 업데이트
function updateAllButtonTitles() {
for (const context of buttonContexts.keys()) {
updateButtonTitle(context);
}
}
// StreamDock 버튼 제목 업데이트
function updateButtonTitle(context) {
if (!websocket || !context) {
console.log('🚫 WebSocket 또는 context 없음 - 제목 업데이트 건너뜀');
return;
}
const settings = getCurrentSettings(context);
const actionType = settings.actionType || 'camera';
let title = 'Camera';
let isActive = true; // 아이템 활성화 상태 (기본값: 활성)
console.log('🏷️ 버튼 제목 업데이트 시작 - Context:', context);
console.log('🏷️ 설정:', settings);
console.log('🏷️ 액션 타입:', actionType);
if (actionType === 'camera') {
const cameraIndex = typeof settings.cameraIndex === 'number' ? settings.cameraIndex : 0;
title = `카메라 ${cameraIndex + 1}`;
// 카메라 목록에서 이름 찾기
if (cameraList && cameraList.length > cameraIndex) {
const camera = cameraList[cameraIndex];
if (camera && camera.name) {
// 카메라 이름에서 불필요한 부분 제거하고 짧게 만들기
let shortName = camera.name
.replace('Cam0', '')
.replace('Cam', '')
.replace('_', ' ')
.substring(0, 10); // 최대 10글자
title = shortName || `카메라 ${cameraIndex + 1}`;
}
// 카메라 활성화 상태 확인
if (typeof camera.isActive === 'boolean') {
isActive = camera.isActive;
}
}
} else if (actionType === 'item') {
const itemIndex = typeof settings.itemIndex === 'number' ? settings.itemIndex : 0;
title = `아이템 ${itemIndex + 1}`;
// 아이템 목록에서 이름 찾기
if (itemList && itemList.length > itemIndex) {
const item = itemList[itemIndex];
if (item && (item.name || item.groupName)) {
// 아이템 이름에서 불필요한 부분 제거하고 짧게 만들기
let itemName = item.name || item.groupName;
let shortName = itemName
.replace('Item', '')
.replace('Group', '')
.replace('_', ' ')
.substring(0, 10); // 최대 10글자
title = shortName || `아이템 ${itemIndex + 1}`;
// 아이템 활성화 상태 확인
if (typeof item.isActive === 'boolean') {
isActive = item.isActive;
}
}
}
} else if (actionType === 'event') {
const eventIndex = typeof settings.eventIndex === 'number' ? settings.eventIndex : 0;
title = `이벤트 ${eventIndex + 1}`;
// 이벤트 목록에서 이름 찾기
if (eventList && eventList.length > eventIndex) {
const event = eventList[eventIndex];
if (event && (event.name || event.groupName)) {
// 이벤트 이름에서 불필요한 부분 제거하고 짧게 만들기
let eventName = event.name || event.groupName;
let shortName = eventName
.replace('Event', '')
.replace('Group', '')
.replace('_', ' ')
.substring(0, 10); // 최대 10글자
title = shortName || `이벤트 ${eventIndex + 1}`;
}
}
} else if (actionType === 'avatar_outfit') {
const avatarIndex = typeof settings.avatarIndex === 'number' ? settings.avatarIndex : 0;
const outfitIndex = typeof settings.outfitIndex === 'number' ? settings.outfitIndex : 0;
title = `아바타 ${avatarIndex + 1}`;
console.log('👗 아바타 제목 업데이트 시작');
console.log('👗 Context:', context);
console.log('👗 현재 설정:', settings);
console.log('👗 아바타 인덱스:', avatarIndex, '의상 인덱스:', outfitIndex);
console.log('👗 Plugin의 avatarOutfitList:', avatarOutfitList);
console.log('👗 avatarOutfitList 길이:', avatarOutfitList ? avatarOutfitList.length : 'null');
// 버튼별 설정 확인
console.log('👗 [DEBUG] 모든 버튼 컨텍스트와 설정:');
for (const [ctx, ctxSettings] of buttonContexts.entries()) {
if (ctxSettings.actionType === 'avatar_outfit') {
console.log(`👗 [DEBUG] Context: ${ctx} -> avatarIndex: ${ctxSettings.avatarIndex}, outfitIndex: ${ctxSettings.outfitIndex}`);
}
}
if (avatarOutfitList && avatarOutfitList.length > avatarIndex) {
const avatar = avatarOutfitList[avatarIndex];
console.log('👗 선택된 아바타:', avatar);
if (avatar && avatar.name) {
console.log('👗 원본 아바타 이름:', avatar.name);
console.log('👗 아바타 이름 타입:', typeof avatar.name);
// 아바타 이름 안전하게 처리
let avatarName = String(avatar.name);
console.log('👗 문자열 변환된 이름:', avatarName);
// 아바타 이름 짧게 만들기
let shortName = avatarName
.replace('Avatar', '')
.replace('Character', '')
.replace('_', ' ')
.substring(0, 8); // 최대 8글자
console.log('👗 처리된 아바타 이름:', shortName);
// 버튼에 설정된 의상 정보 추가
let outfitInfo = '';
if (avatar.outfits && avatar.outfits.length > outfitIndex) {
const outfit = avatar.outfits[outfitIndex];
console.log('👗 버튼에 설정된 의상:', outfit);
if (outfit && outfit.name) {
outfitInfo = '\n' + String(outfit.name).substring(0, 6);
console.log('👗 설정된 의상 이름:', outfit.name);
}
} else {
console.log('👗 의상 인덱스가 범위를 벗어남 또는 의상 목록이 없음');
console.log('👗 outfitIndex:', outfitIndex, 'outfits 길이:', avatar.outfits ? avatar.outfits.length : 'null');
}
title = (shortName || `아바타${avatarIndex + 1}`) + outfitInfo;
console.log('👗 최종 제목:', title);
// 아바타 의상 활성화 상태 확인
if (avatar.outfits && avatar.outfits.length > outfitIndex) {
const outfit = avatar.outfits[outfitIndex];
// 현재 활성화된 의상이 설정된 의상과 같으면 활성 상태
isActive = (avatar.current_outfit_index === outfitIndex);
console.log('👗 아바타 의상 활성 상태:', isActive,
'current:', avatar.current_outfit_index, 'target:', outfitIndex);
} else {
isActive = false;
console.log('👗 의상을 찾을 수 없어 비활성 상태로 설정');
}
} else {
console.log('👗 아바타 이름이 없음:', avatar);
isActive = false;
}
} else {
console.log('👗 아바타 목록이 없거나 인덱스가 범위를 벗어남');
console.log('👗 목록 길이:', avatarOutfitList ? avatarOutfitList.length : 'null');
}
} else if (actionType === 'optitrack_marker_toggle') {
// OptiTrack 마커 토글 버튼
const currentState = settings.currentState || 0;
title = currentState === 0 ? '마커\nON' : '마커\nOFF';
isActive = true; // 항상 활성 상태
console.log('🎯 OptiTrack 마커 버튼 제목:', title, '(State:', currentState, ')');
} else if (actionType === 'motion_recording_toggle') {
// 모션 녹화 토글 버튼
const currentState = settings.currentState || 0;
title = currentState === 0 ? '녹화\nOFF' : '녹화\nREC';
isActive = true; // 항상 활성 상태
console.log('🎬 모션 녹화 버튼 제목:', title, '(State:', currentState, ')');
} else if (actionType === 'optitrack_reconnect') {
// OptiTrack 재접속 버튼
title = '옵티트랙\n재접속';
isActive = true; // 항상 활성 상태
console.log('🔄 OptiTrack 재접속 버튼 제목:', title);
} else if (actionType === 'facial_motion_reconnect') {
// Facial Motion 재접속 버튼
title = '페이셜\n재접속';
isActive = true; // 항상 활성 상태
console.log('😊 Facial Motion 재접속 버튼 제목:', title);
} else if (actionType === 'screenshot') {
// 스크린샷 버튼
title = '스크린샷';
isActive = true; // 항상 활성 상태
console.log('📸 스크린샷 버튼 제목:', title);
} else if (actionType === 'screenshot_alpha') {
// 알파 스크린샷 버튼
title = '알파\n스크린샷';
isActive = true; // 항상 활성 상태
console.log('📷 알파 스크린샷 버튼 제목:', title);
}
// StreamDock에 제목 업데이트 요청
const message = {
event: 'setTitle',
context: context,
payload: {
title: title,
target: 0, // hardware and software
titleParameters: {
fontSize: 36, // 기존 24 → 36으로 키움
showTitle: true,
titleAlignment: "bottom" // 중앙(middle) → 아래(bottom)으로 변경
}
}
};
websocket.send(JSON.stringify(message));
console.log('🏷️ 버튼 제목 업데이트:', title, '(액션 타입:', actionType, ', 활성:', isActive, ')');
// 아이템, 카메라, 아바타 의상이 비활성화되어 있으면 아이콘을 어둡게 표시 (이벤트는 제외)
if ((actionType === 'item' || actionType === 'camera' || actionType === 'avatar_outfit') && !isActive) {
setButtonState(context, false); // 비활성 상태로 설정
} else if (actionType === 'item' || actionType === 'camera' || actionType === 'avatar_outfit') {
setButtonState(context, true); // 활성 상태로 설정
}
// 이벤트 컨트롤러는 활성/비활성 상태 변경 없음 (항상 활성)
}
// 버튼 상태 설정 (활성/비활성)
function setButtonState(context, isActive) {
if (!websocket || !context) {
console.log('🚫 WebSocket 또는 context 없음 - 버튼 상태 업데이트 건너뜀');
return;
}
const settings = getCurrentSettings(context);
const actionType = settings.actionType || 'camera';
// 아이템, 카메라, 아바타 의상 컨트롤러만 상태 변경 적용 (이벤트 컨트롤러는 제외)
if (actionType === 'item' || actionType === 'camera' || actionType === 'avatar_outfit') {
// 방법 1: setState 이벤트 사용
const stateMessage = {
event: 'setState',
context: context,
payload: {
state: isActive ? 0 : 1, // 0: 활성 상태, 1: 비활성 상태
target: 0 // hardware and software
}
};
websocket.send(JSON.stringify(stateMessage));
console.log('🎨 버튼 상태 업데이트 (setState):', context, '(활성:', isActive, ', 상태:', isActive ? 0 : 1, ')');
console.log('🎨 버튼 상태 업데이트 완료:', context, '(활성:', isActive, ')');
}
}
// 설정 관리 헬퍼 함수들
function getCurrentSettings(context) {
if (!context) return {};
return buttonContexts.get(context) || {};
}
function setCurrentSettings(context, newSettings) {
if (!context) return;
buttonContexts.set(context, newSettings);
}
function updateCurrentSettings(context, partialSettings) {
if (!context) return;
const currentSettings = getCurrentSettings(context);
setCurrentSettings(context, { ...currentSettings, ...partialSettings });
}
// 브라우저 환경에서 자동 시작
console.log('🌐 브라우저 기반 플러그인 준비 완료');