Fix : 카메라 컨트롤러 활성 비활성 디스플레이 기능 추가 패치

This commit is contained in:
KINDNICK 2025-07-08 00:37:29 +09:00
parent 650c3927bc
commit 624e117a2a
5 changed files with 249 additions and 100 deletions

View File

@ -102,7 +102,8 @@ public class StreamDeckServerManager : MonoBehaviour
// 메인 스레드에서 처리하도록 큐에 추가
lock (lockObject)
{
mainThreadActions.Enqueue(() => {
mainThreadActions.Enqueue(() =>
{
connectedClients.Add(service);
Debug.Log($"[StreamDeckServerManager] 클라이언트 연결됨. 총 연결: {connectedClients.Count}");
SendInitialCameraData(service);
@ -115,7 +116,8 @@ public class StreamDeckServerManager : MonoBehaviour
{
lock (lockObject)
{
mainThreadActions.Enqueue(() => {
mainThreadActions.Enqueue(() =>
{
try
{
ProcessMessage(messageData, service);
@ -298,9 +300,6 @@ public class StreamDeckServerManager : MonoBehaviour
{
Debug.Log($"[StreamDeckServerManager] 카메라 {cameraIndex}번으로 전환");
cameraManager.Set(cameraIndex);
// 카메라 변경 알림 전송
NotifyCameraChanged();
}
else
{
@ -329,9 +328,6 @@ public class StreamDeckServerManager : MonoBehaviour
{
Debug.Log($"[StreamDeckServerManager] 카메라 {cameraIndex}번으로 전환");
cameraManager.Set(cameraIndex);
// 카메라 변경 알림 전송
NotifyCameraChanged();
}
else
{

View File

@ -1,11 +1,90 @@
const { Plugins, Actions, log, EventEmitter } = require('./utils/plugin');
const { execSync } = require('child_process');
const WebSocket = require('ws');
const fs = require('fs');
const path = require('path');
const plugin = new Plugins('demo');
// 로그 파일 경로 설정
const logFilePath = path.join(__dirname, 'streamdeck_plugin.log');
// 로그 함수 - 기존 log 시스템과 함께 사용
function writeLog(message) {
const timestamp = new Date().toISOString();
const logMessage = `[${timestamp}] ${message}`;
// 기존 log 시스템 사용
log.info(logMessage);
// 파일에도 기록
try {
fs.appendFileSync(logFilePath, logMessage + '\n');
} catch (err) {
log.error('로그 파일 쓰기 실패:', err.message);
}
}
const counters = {};
let unityWebSocket = null;
let reconnectTimer = null;
// 유니티 WebSocket 서버 연결 함수
function connectToUnity() {
if (unityWebSocket) {
unityWebSocket.close();
}
unityWebSocket = new WebSocket('ws://localhost:10701/');
unityWebSocket.on('open', function() {
writeLog('유니티 WebSocket 서버에 연결됨');
});
unityWebSocket.on('message', function(data) {
writeLog('유니티로부터 메시지 수신: ' + data.toString());
});
unityWebSocket.on('error', function(err) {
writeLog('유니티 WebSocket 연결 오류: ' + err.message);
scheduleReconnect();
});
unityWebSocket.on('close', function() {
writeLog('유니티 WebSocket 연결 종료');
scheduleReconnect();
});
}
// 재연결 스케줄링
function scheduleReconnect() {
if (reconnectTimer) {
clearTimeout(reconnectTimer);
}
reconnectTimer = setTimeout(() => {
writeLog('유니티 WebSocket 서버 재연결 시도...');
connectToUnity();
}, 3000);
}
// 유니티로 메시지 전송
function sendToUnity(message) {
if (unityWebSocket && unityWebSocket.readyState === WebSocket.OPEN) {
unityWebSocket.send(message);
writeLog('유니티 WebSocket 서버로 메시지 전송: ' + message);
return true;
} else {
writeLog('유니티 WebSocket 서버 연결이 없거나 열려있지 않음');
return false;
}
}
plugin.didReceiveGlobalSettings = ({ payload: { settings } }) => {
log.info('didReceiveGlobalSettings', settings);
writeLog('플러그인 설정 수신됨');
// 플러그인 시작 시 유니티 WebSocket 서버 연결
connectToUnity();
};
const createSvg = (text) => `<svg width="144" height="144" xmlns="http://www.w3.org/2000/svg">
@ -20,22 +99,30 @@ plugin.demo = new Actions({
default: {
},
async _willAppear({ context, payload }) {
// log.info("demo: ", context);
let n = 0;
timers[context] = setInterval(async () => {
const svg = createSvg(++n);
// 버튼이 처음 나타날 때 카운터 초기화 및 이미지 표시
counters[context] = 0;
const svg = createSvg(counters[context]);
plugin.setImage(context, `data:image/svg+xml;charset=utf8,${svg}`);
}, 1000);
writeLog(`버튼 나타남, context: ${context}`);
},
_willDisappear({ context }) {
// log.info('willDisAppear', context)
timers[context] && clearInterval(timers[context]);
delete counters[context];
writeLog(`버튼 사라짐, context: ${context}`);
},
_propertyInspectorDidAppear({ context }) {
},
sendToPlugin({ payload, context }) {
},
keyUp({ context, payload }) {
// 카운터 증가
if (counters[context] === undefined) counters[context] = 0;
counters[context]++;
const svg = createSvg(counters[context]);
plugin.setImage(context, `data:image/svg+xml;charset=utf8,${svg}`);
writeLog(`버튼 클릭됨, context: ${context}, count: ${counters[context]}`);
// 유니티 WebSocket 서버로 메시지 전송
sendToUnity('Hello Unity from StreamDeck!');
},
dialDown({ context, payload }) {},
dialRotate({ context, payload }) {}

Binary file not shown.

Binary file not shown.

View File

@ -9,6 +9,18 @@ let isUnityConnected = false;
let cameraList = []; // 카메라 목록 저장
let itemList = []; // 아이템 목록 저장
const fs = require('fs');
function getBase64Image(filePath) {
try {
const data = fs.readFileSync(filePath);
return 'data:image/png;base64,' + data.toString('base64');
} catch (e) {
console.error('이미지 파일 읽기 실패:', filePath, e);
return '';
}
}
// StreamDock 연결 함수 (브라우저 기반)
function connectElgatoStreamDeckSocket(inPort, inUUID, inEvent, inInfo, inActionInfo) {
console.log('🔌 StreamDock 연결 시작 (브라우저)');
@ -576,6 +588,12 @@ function handleUnityMessage(data) {
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 {
@ -586,7 +604,41 @@ function handleUnityMessage(data) {
break;
case 'camera_changed':
console.log('🎯 카메라 변경 알림');
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':
@ -705,6 +757,11 @@ function updateButtonTitle(context) {
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;
@ -750,8 +807,8 @@ function updateButtonTitle(context) {
websocket.send(JSON.stringify(message));
console.log('🏷️ 버튼 제목 업데이트:', title, '(액션 타입:', actionType, ', 활성:', isActive, ')');
// 아이템이 비활성화되어 있으면 아이콘을 어둡게 표시
if (actionType === 'item' && !isActive) {
// 아이템이나 카메라가 비활성화되어 있으면 아이콘을 어둡게 표시
if ((actionType === 'item' || actionType === 'camera') && !isActive) {
setButtonState(context, false); // 비활성 상태로 설정
} else {
setButtonState(context, true); // 활성 상태로 설정
@ -768,8 +825,8 @@ function setButtonState(context, isActive) {
const settings = getCurrentSettings(context);
const actionType = settings.actionType || 'camera';
// 아이템 컨트롤러 상태 변경 적용
if (actionType === 'item') {
// 아이템 컨트롤러와 카메라 컨트롤러 모두 상태 변경 적용
if (actionType === 'item' || actionType === 'camera') {
// 방법 1: setState 이벤트 사용
const stateMessage = {
event: 'setState',
@ -783,19 +840,25 @@ function setButtonState(context, isActive) {
websocket.send(JSON.stringify(stateMessage));
console.log('🎨 버튼 상태 업데이트 (setState):', context, '(활성:', isActive, ', 상태:', isActive ? 0 : 1, ')');
// 방법 2: setImage 이벤트로 아이콘 직접 변경
const imageName = isActive ? 'item_icon.png' : 'item_icon_inactive.png';
// 방법 2: setImage 이벤트로 아이콘 직접 변경 (base64)
let imagePath;
if (actionType === 'item') {
imagePath = isActive ? 'images/item_icon.png' : 'images/item_icon_inactive.png';
} else if (actionType === 'camera') {
imagePath = isActive ? 'images/camera_icon.png' : 'images/camera_icon_inactive.png';
}
const imageBase64 = getBase64Image(imagePath);
const imageMessage = {
event: 'setImage',
context: context,
payload: {
image: imageName,
image: imageBase64,
target: 0 // hardware and software
}
};
websocket.send(JSON.stringify(imageMessage));
console.log('🖼️ 버튼 아이콘 업데이트 (setImage):', context, '(활성:', isActive, ', 이미지:', imageName, ')');
console.log('🖼️ 버튼 아이콘 업데이트 (setImage):', context, '(활성:', isActive, ', 이미지:', imagePath, ')');
// 추가 디버깅을 위한 로그
setTimeout(() => {