- 모든 컨트롤러 에디터를 IMGUI → UI Toolkit(UXML/USS)으로 전환 (Camera, Item, Event, Avatar, System, StreamDeck, OptiTrack, Facial) - StreamingleCommon.uss 공통 테마 + 개별 에디터 USS 스타일시트 - SystemController 서브매니저 분리 (OptiTrack, Facial, Recording, Screenshot 등) - 런타임 컨트롤 패널 (ESC 토글, 좌측 오버레이, 150% 스케일) - 웹 대시보드 서버 (StreamingleDashboardServer) + 리타게팅 통합 - 설정 도구(StreamingleControllerSetupTool) UXML 재작성 + 원클릭 설정 - SimplePoseTransfer UXML 에디터 추가 - 전체 UXML 한글화 + NanumGothic 폰트 적용 - Streamingle.Debug → Streamingle.Debugging 네임스페이스 변경 (Debug.Log 충돌 해결) - 불필요 코드 제거 (rawkey.cs, RetargetingHTTPServer, OptitrackSkeletonAnimator 등) Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
745 lines
24 KiB
C#
745 lines
24 KiB
C#
using UnityEngine;
|
|
using WebSocketSharp.Server;
|
|
using System.Collections.Generic;
|
|
using Newtonsoft.Json;
|
|
using System;
|
|
using System.Linq;
|
|
|
|
public class StreamDeckServerManager : MonoBehaviour
|
|
{
|
|
[Header("WebSocket 서버 설정")]
|
|
public int port = 64211;
|
|
|
|
[Header("대시보드 설정")]
|
|
public int dashboardPort = 64210;
|
|
public bool enableDashboard = true;
|
|
|
|
private WebSocketServer server;
|
|
private StreamingleDashboardServer dashboardServer;
|
|
private List<StreamDeckService> connectedClients = new List<StreamDeckService>();
|
|
|
|
public CameraManager cameraManager { get; private set; }
|
|
public ItemController itemController { get; private set; }
|
|
public EventController eventController { get; private set; }
|
|
public AvatarOutfitController avatarOutfitController { get; private set; }
|
|
public SystemController systemController { get; private set; }
|
|
|
|
public static StreamDeckServerManager Instance { get; private set; }
|
|
|
|
private readonly Queue<System.Action> mainThreadActions = new Queue<System.Action>();
|
|
private readonly object lockObject = new object();
|
|
|
|
public int ConnectedClientCount => connectedClients.Count;
|
|
|
|
void Awake()
|
|
{
|
|
Instance = this;
|
|
}
|
|
|
|
void Start()
|
|
{
|
|
cameraManager = FindObjectOfType<CameraManager>();
|
|
if (cameraManager == null)
|
|
{
|
|
Debug.LogError("[StreamDeckServerManager] CameraManager를 찾을 수 없습니다!");
|
|
return;
|
|
}
|
|
|
|
itemController = FindObjectOfType<ItemController>();
|
|
if (itemController == null)
|
|
Debug.LogWarning("[StreamDeckServerManager] ItemController를 찾을 수 없습니다. 아이템 컨트롤 기능이 비활성화됩니다.");
|
|
|
|
eventController = FindObjectOfType<EventController>();
|
|
if (eventController == null)
|
|
Debug.LogWarning("[StreamDeckServerManager] EventController를 찾을 수 없습니다. 이벤트 컨트롤 기능이 비활성화됩니다.");
|
|
|
|
avatarOutfitController = FindObjectOfType<AvatarOutfitController>();
|
|
if (avatarOutfitController == null)
|
|
Debug.LogWarning("[StreamDeckServerManager] AvatarOutfitController를 찾을 수 없습니다. 아바타 의상 컨트롤 기능이 비활성화됩니다.");
|
|
|
|
systemController = FindObjectOfType<SystemController>();
|
|
if (systemController == null)
|
|
Debug.LogWarning("[StreamDeckServerManager] SystemController를 찾을 수 없습니다. 시스템 컨트롤 기능이 비활성화됩니다.");
|
|
|
|
StartServer();
|
|
StartDashboardServer();
|
|
}
|
|
|
|
void Update()
|
|
{
|
|
lock (lockObject)
|
|
{
|
|
while (mainThreadActions.Count > 0)
|
|
{
|
|
var action = mainThreadActions.Dequeue();
|
|
try
|
|
{
|
|
action?.Invoke();
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
Debug.LogError($"[StreamDeckServerManager] 메인 스레드 작업 실행 오류: {ex.Message}");
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void OnApplicationQuit()
|
|
{
|
|
StopServer();
|
|
StopDashboardServer();
|
|
}
|
|
|
|
#region Server Lifecycle
|
|
private void StartServer()
|
|
{
|
|
try
|
|
{
|
|
// 0.0.0.0 으로 바인딩하여 LAN 내 다른 기기에서도 접속 가능
|
|
server = new WebSocketServer(port);
|
|
server.AddWebSocketService<StreamDeckService>("/");
|
|
server.Start();
|
|
Debug.Log($"[StreamDeckServerManager] WebSocket 서버 시작됨, 포트: {port} (모든 인터페이스)");
|
|
}
|
|
catch (Exception e)
|
|
{
|
|
Debug.LogError($"[StreamDeckServerManager] 서버 시작 실패: {e.Message}");
|
|
}
|
|
}
|
|
|
|
private void StopServer()
|
|
{
|
|
if (server != null)
|
|
{
|
|
server.Stop();
|
|
server = null;
|
|
Debug.Log("[StreamDeckServerManager] WebSocket 서버 중지됨");
|
|
}
|
|
}
|
|
|
|
private void StartDashboardServer()
|
|
{
|
|
if (!enableDashboard) return;
|
|
|
|
int retargetingWsPort = systemController?.retargetingRemote?.retargetingWsPort ?? 0;
|
|
dashboardServer = new StreamingleDashboardServer(dashboardPort, port, retargetingWsPort);
|
|
dashboardServer.Start();
|
|
}
|
|
|
|
private void StopDashboardServer()
|
|
{
|
|
dashboardServer?.Stop();
|
|
dashboardServer = null;
|
|
}
|
|
#endregion
|
|
|
|
#region Client Management
|
|
public void OnClientConnected(StreamDeckService service)
|
|
{
|
|
lock (lockObject)
|
|
{
|
|
mainThreadActions.Enqueue(() =>
|
|
{
|
|
connectedClients.Add(service);
|
|
Debug.Log($"[StreamDeckServerManager] 클라이언트 연결됨. 총 연결: {connectedClients.Count}");
|
|
SendInitialData(service);
|
|
});
|
|
}
|
|
}
|
|
|
|
public void ProcessMessageOnMainThread(string messageData, StreamDeckService service)
|
|
{
|
|
lock (lockObject)
|
|
{
|
|
mainThreadActions.Enqueue(() =>
|
|
{
|
|
try
|
|
{
|
|
ProcessMessage(messageData, service);
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
Debug.LogError($"[StreamDeckServerManager] 메시지 처리 오류: {ex.Message}");
|
|
}
|
|
});
|
|
}
|
|
}
|
|
|
|
public void OnClientDisconnected(StreamDeckService service)
|
|
{
|
|
connectedClients.Remove(service);
|
|
Debug.Log($"[StreamDeckServerManager] 클라이언트 연결 해제됨. 총 연결: {connectedClients.Count}");
|
|
}
|
|
|
|
public void BroadcastMessage(string message)
|
|
{
|
|
foreach (var client in connectedClients.ToArray())
|
|
{
|
|
try
|
|
{
|
|
client.SendMessage(message);
|
|
}
|
|
catch (Exception e)
|
|
{
|
|
Debug.LogError($"[StreamDeckServerManager] 메시지 전송 실패: {e.Message}");
|
|
connectedClients.Remove(client);
|
|
}
|
|
}
|
|
}
|
|
#endregion
|
|
|
|
#region Initial Data
|
|
private void SendInitialData(StreamDeckService service)
|
|
{
|
|
if (cameraManager == null) return;
|
|
|
|
var initialData = new
|
|
{
|
|
type = "connection_established",
|
|
timestamp = DateTime.UtcNow.ToString("o"),
|
|
version = "1.0",
|
|
data = new
|
|
{
|
|
session_id = Guid.NewGuid().ToString(),
|
|
message = "유니티 서버에 연결되었습니다!",
|
|
camera_data = cameraManager.GetCameraListData(),
|
|
current_camera = cameraManager.GetCurrentCameraState(),
|
|
item_data = itemController?.GetItemListData(),
|
|
current_item = itemController?.GetCurrentItemState(),
|
|
event_data = eventController?.GetEventListData(),
|
|
current_event = eventController?.GetCurrentEventState(),
|
|
avatar_outfit_data = avatarOutfitController?.GetAvatarOutfitListData(),
|
|
current_avatar_outfit = avatarOutfitController?.GetCurrentAvatarOutfitState()
|
|
}
|
|
};
|
|
|
|
string json = JsonConvert.SerializeObject(initialData);
|
|
service.SendMessage(json);
|
|
Debug.Log("[StreamDeckServerManager] 초기 데이터 전송됨");
|
|
}
|
|
#endregion
|
|
|
|
#region Broadcast Notifications
|
|
public void NotifyCameraChanged()
|
|
{
|
|
if (cameraManager == null) return;
|
|
|
|
BroadcastJson(new
|
|
{
|
|
type = "camera_changed",
|
|
timestamp = DateTime.UtcNow.ToString("o"),
|
|
version = "1.0",
|
|
data = new
|
|
{
|
|
camera_data = cameraManager.GetCameraListData(),
|
|
current_camera = cameraManager.GetCurrentCameraState()
|
|
}
|
|
});
|
|
}
|
|
|
|
public void NotifyItemChanged()
|
|
{
|
|
if (itemController == null) return;
|
|
|
|
BroadcastJson(new
|
|
{
|
|
type = "item_changed",
|
|
timestamp = DateTime.UtcNow.ToString("o"),
|
|
version = "1.0",
|
|
data = new
|
|
{
|
|
item_data = itemController.GetItemListData(),
|
|
current_item = itemController.GetCurrentItemState()
|
|
}
|
|
});
|
|
}
|
|
|
|
public void NotifyEventChanged()
|
|
{
|
|
if (eventController == null) return;
|
|
|
|
BroadcastJson(new
|
|
{
|
|
type = "event_changed",
|
|
timestamp = DateTime.UtcNow.ToString("o"),
|
|
version = "1.0",
|
|
data = new
|
|
{
|
|
event_data = eventController.GetEventListData(),
|
|
current_event = eventController.GetCurrentEventState()
|
|
}
|
|
});
|
|
}
|
|
|
|
public void NotifyAvatarOutfitChanged()
|
|
{
|
|
if (avatarOutfitController == null) return;
|
|
|
|
BroadcastJson(new
|
|
{
|
|
type = "avatar_outfit_changed",
|
|
timestamp = DateTime.UtcNow.ToString("o"),
|
|
version = "1.0",
|
|
data = new
|
|
{
|
|
avatar_outfit_data = avatarOutfitController.GetAvatarOutfitListData(),
|
|
current_avatar_outfit = avatarOutfitController.GetCurrentAvatarOutfitState()
|
|
}
|
|
});
|
|
}
|
|
|
|
private void BroadcastJson(object data)
|
|
{
|
|
string json = JsonConvert.SerializeObject(data);
|
|
BroadcastMessage(json);
|
|
}
|
|
#endregion
|
|
|
|
#region Message Processing
|
|
private void ProcessMessage(string messageData, StreamDeckService service)
|
|
{
|
|
var message = JsonConvert.DeserializeObject<Dictionary<string, object>>(messageData);
|
|
string messageType = message.ContainsKey("type") ? message["type"].ToString() : null;
|
|
|
|
switch (messageType)
|
|
{
|
|
// 카메라
|
|
case "switch_camera":
|
|
HandleWithIndex(message, "camera_index", cameraManager, "cameraManager",
|
|
cameraManager?.cameraPresets?.Count ?? 0,
|
|
idx => { cameraManager.Set(idx); });
|
|
break;
|
|
case "get_camera_list":
|
|
HandleGetCameraList(service);
|
|
break;
|
|
case "toggle_drone_mode":
|
|
HandleToggleDroneMode(service);
|
|
break;
|
|
case "get_drone_state":
|
|
HandleGetDroneState(service);
|
|
break;
|
|
|
|
// 아이템
|
|
case "toggle_item":
|
|
HandleWithIndex(message, "item_index", itemController, "itemController",
|
|
itemController?.itemGroups?.Count ?? 0,
|
|
idx => { itemController.ToggleGroup(idx); NotifyItemChanged(); });
|
|
break;
|
|
case "set_item":
|
|
HandleWithIndex(message, "item_index", itemController, "itemController",
|
|
itemController?.itemGroups?.Count ?? 0,
|
|
idx => { itemController.Set(idx); NotifyItemChanged(); });
|
|
break;
|
|
case "get_item_list":
|
|
HandleGetItemList(service);
|
|
break;
|
|
|
|
// 이벤트
|
|
case "execute_event":
|
|
HandleWithIndex(message, "event_index", eventController, "eventController",
|
|
eventController?.eventGroups?.Count ?? 0,
|
|
idx => { eventController.ExecuteEvent(idx); });
|
|
break;
|
|
case "set_event":
|
|
HandleWithIndex(message, "event_index", eventController, "eventController",
|
|
eventController?.eventGroups?.Count ?? 0,
|
|
idx => { eventController.Set(idx); });
|
|
break;
|
|
case "get_event_list":
|
|
HandleGetEventList(service);
|
|
break;
|
|
|
|
// 아바타 의상
|
|
case "set_avatar_outfit":
|
|
HandleSetAvatarOutfit(message);
|
|
break;
|
|
case "get_avatar_outfit_list":
|
|
HandleGetAvatarOutfitList(service);
|
|
break;
|
|
|
|
// 시스템 상태 (대시보드용 신규)
|
|
case "get_system_status":
|
|
HandleGetSystemStatus(service);
|
|
break;
|
|
case "get_full_state":
|
|
SendInitialData(service);
|
|
break;
|
|
case "ping":
|
|
SendJson(service, new { type = "pong", timestamp = DateTime.UtcNow.ToString("o") });
|
|
break;
|
|
|
|
// SystemController 명령어들
|
|
case "toggle_optitrack_markers":
|
|
case "show_optitrack_markers":
|
|
case "hide_optitrack_markers":
|
|
case "reconnect_optitrack":
|
|
case "spawn_optitrack_client":
|
|
case "reconnect_facial_motion":
|
|
case "refresh_facial_motion_clients":
|
|
case "start_motion_recording":
|
|
case "stop_motion_recording":
|
|
case "toggle_motion_recording":
|
|
case "refresh_motion_recorders":
|
|
case "capture_screenshot":
|
|
case "capture_alpha_screenshot":
|
|
case "open_screenshot_folder":
|
|
case "refresh_avatar_head_colliders":
|
|
case "refresh_magica_cloth":
|
|
case "reset_magica_cloth":
|
|
case "reset_magica_cloth_keep_pose":
|
|
case "start_retargeting_remote":
|
|
case "stop_retargeting_remote":
|
|
case "toggle_retargeting_remote":
|
|
case "refresh_retargeting_characters":
|
|
HandleSystemCommand(message);
|
|
break;
|
|
|
|
case "test":
|
|
SendJson(service, new
|
|
{
|
|
type = "echo",
|
|
timestamp = DateTime.UtcNow.ToString("o"),
|
|
data = new { received_message = messageData }
|
|
});
|
|
break;
|
|
|
|
default:
|
|
Debug.Log($"[StreamDeckServerManager] 알 수 없는 메시지 타입: {messageType}");
|
|
break;
|
|
}
|
|
}
|
|
#endregion
|
|
|
|
#region Index Extraction Helper
|
|
/// <summary>
|
|
/// 메시지에서 인덱스를 추출합니다. JObject와 Dictionary 모두 지원합니다.
|
|
/// </summary>
|
|
private int? ExtractIndex(Dictionary<string, object> message, string key)
|
|
{
|
|
if (!message.ContainsKey("data")) return null;
|
|
|
|
var dataObject = message["data"];
|
|
|
|
string rawValue = null;
|
|
|
|
if (dataObject is Newtonsoft.Json.Linq.JObject jObject)
|
|
{
|
|
if (jObject.ContainsKey(key))
|
|
rawValue = jObject[key]?.ToString();
|
|
}
|
|
else if (dataObject is Dictionary<string, object> data)
|
|
{
|
|
if (data.ContainsKey(key))
|
|
rawValue = data[key]?.ToString();
|
|
}
|
|
|
|
if (rawValue != null && int.TryParse(rawValue, out int index))
|
|
return index;
|
|
|
|
return null;
|
|
}
|
|
|
|
/// <summary>
|
|
/// 인덱스 기반 핸들러를 간결하게 처리합니다.
|
|
/// </summary>
|
|
private void HandleWithIndex(Dictionary<string, object> message, string indexKey,
|
|
object controller, string controllerName, int maxCount, Action<int> action)
|
|
{
|
|
if (controller == null)
|
|
{
|
|
Debug.LogError($"[StreamDeckServerManager] {controllerName}가 null입니다!");
|
|
return;
|
|
}
|
|
|
|
int? index = ExtractIndex(message, indexKey);
|
|
if (index == null)
|
|
{
|
|
Debug.LogError($"[StreamDeckServerManager] '{indexKey}' 파싱 실패");
|
|
return;
|
|
}
|
|
|
|
if (index.Value < 0 || index.Value >= maxCount)
|
|
{
|
|
Debug.LogError($"[StreamDeckServerManager] 잘못된 인덱스: {index.Value}, 유효 범위: 0-{maxCount - 1}");
|
|
return;
|
|
}
|
|
|
|
action(index.Value);
|
|
}
|
|
#endregion
|
|
|
|
#region Response Helpers
|
|
private void SendJson(StreamDeckService service, object data)
|
|
{
|
|
string json = JsonConvert.SerializeObject(data);
|
|
service.SendMessage(json);
|
|
}
|
|
#endregion
|
|
|
|
#region Camera Handlers
|
|
private void HandleGetCameraList(StreamDeckService service)
|
|
{
|
|
if (cameraManager == null) return;
|
|
|
|
SendJson(service, new
|
|
{
|
|
type = "camera_list_response",
|
|
timestamp = DateTime.UtcNow.ToString("o"),
|
|
version = "1.0",
|
|
data = new
|
|
{
|
|
camera_data = cameraManager.GetCameraListData(),
|
|
current_camera = cameraManager.GetCurrentCameraState()
|
|
}
|
|
});
|
|
}
|
|
|
|
private void HandleToggleDroneMode(StreamDeckService service)
|
|
{
|
|
if (cameraManager == null) return;
|
|
cameraManager.ToggleDroneMode();
|
|
HandleGetDroneState(service);
|
|
}
|
|
|
|
private void HandleGetDroneState(StreamDeckService service)
|
|
{
|
|
if (cameraManager == null) return;
|
|
|
|
SendJson(service, new
|
|
{
|
|
type = "drone_state_response",
|
|
timestamp = DateTime.UtcNow.ToString("o"),
|
|
version = "1.0",
|
|
data = new
|
|
{
|
|
is_drone_mode = cameraManager.IsDroneModeActive,
|
|
current_camera = cameraManager.GetCurrentCameraState()
|
|
}
|
|
});
|
|
}
|
|
#endregion
|
|
|
|
#region Item/Event/Avatar Handlers
|
|
private void HandleGetItemList(StreamDeckService service)
|
|
{
|
|
if (itemController == null) return;
|
|
|
|
SendJson(service, new
|
|
{
|
|
type = "item_list_response",
|
|
timestamp = DateTime.UtcNow.ToString("o"),
|
|
version = "1.0",
|
|
data = new
|
|
{
|
|
item_data = itemController.GetItemListData(),
|
|
current_item = itemController.GetCurrentItemState()
|
|
}
|
|
});
|
|
}
|
|
|
|
private void HandleGetEventList(StreamDeckService service)
|
|
{
|
|
if (eventController == null) return;
|
|
|
|
SendJson(service, new
|
|
{
|
|
type = "event_list_response",
|
|
timestamp = DateTime.UtcNow.ToString("o"),
|
|
version = "1.0",
|
|
data = new
|
|
{
|
|
event_data = eventController.GetEventListData(),
|
|
current_event = eventController.GetCurrentEventState()
|
|
}
|
|
});
|
|
}
|
|
|
|
private void HandleSetAvatarOutfit(Dictionary<string, object> message)
|
|
{
|
|
if (avatarOutfitController == null)
|
|
{
|
|
Debug.LogError("[StreamDeckServerManager] avatarOutfitController가 null입니다!");
|
|
return;
|
|
}
|
|
|
|
int? avatarIndex = ExtractIndex(message, "avatar_index");
|
|
int? outfitIndex = ExtractIndex(message, "outfit_index");
|
|
|
|
if (avatarIndex == null || outfitIndex == null)
|
|
{
|
|
Debug.LogError("[StreamDeckServerManager] avatar_index 또는 outfit_index 파싱 실패");
|
|
return;
|
|
}
|
|
|
|
if (avatarIndex.Value < 0 || avatarIndex.Value >= (avatarOutfitController.avatars?.Count ?? 0))
|
|
{
|
|
Debug.LogError($"[StreamDeckServerManager] 잘못된 아바타 인덱스: {avatarIndex.Value}");
|
|
return;
|
|
}
|
|
|
|
Debug.Log($"[StreamDeckServerManager] 아바타 {avatarIndex.Value}번 의상을 {outfitIndex.Value}번으로 설정");
|
|
avatarOutfitController.SetAvatarOutfit(avatarIndex.Value, outfitIndex.Value);
|
|
}
|
|
|
|
private void HandleGetAvatarOutfitList(StreamDeckService service)
|
|
{
|
|
if (avatarOutfitController == null)
|
|
{
|
|
Debug.LogError("[StreamDeckServerManager] avatarOutfitController가 null입니다!");
|
|
return;
|
|
}
|
|
|
|
SendJson(service, new
|
|
{
|
|
type = "avatar_outfit_list_response",
|
|
timestamp = DateTime.UtcNow.ToString("o"),
|
|
version = "1.0",
|
|
data = new
|
|
{
|
|
avatar_outfit_data = avatarOutfitController.GetAvatarOutfitListData(),
|
|
current_avatar_outfit = avatarOutfitController.GetCurrentAvatarOutfitState()
|
|
}
|
|
});
|
|
}
|
|
#endregion
|
|
|
|
#region System Command Handler
|
|
private void HandleSystemCommand(Dictionary<string, object> message)
|
|
{
|
|
string messageType = message.ContainsKey("type") ? message["type"].ToString() : null;
|
|
|
|
if (systemController == null)
|
|
{
|
|
Debug.LogError("[StreamDeckServerManager] SystemController가 null입니다!");
|
|
return;
|
|
}
|
|
|
|
try
|
|
{
|
|
Dictionary<string, object> parameters = new Dictionary<string, object>();
|
|
|
|
if (message.ContainsKey("data"))
|
|
{
|
|
var dataObject = message["data"];
|
|
if (dataObject is Newtonsoft.Json.Linq.JObject jObject)
|
|
{
|
|
foreach (var prop in jObject.Properties())
|
|
{
|
|
parameters[prop.Name] = prop.Value.ToString();
|
|
}
|
|
}
|
|
}
|
|
|
|
systemController.ExecuteCommand(messageType, parameters);
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
Debug.LogError($"[StreamDeckServerManager] 시스템 명령어 실행 실패: {ex.Message}");
|
|
}
|
|
}
|
|
#endregion
|
|
|
|
#region System Status (Dashboard)
|
|
private void HandleGetSystemStatus(StreamDeckService service)
|
|
{
|
|
var statusData = new Dictionary<string, object>();
|
|
|
|
// OptiTrack
|
|
if (systemController != null)
|
|
{
|
|
statusData["optitrack"] = new
|
|
{
|
|
connected = systemController.optiTrack.IsOptitrackConnected(),
|
|
status = systemController.optiTrack.GetOptitrackConnectionStatus()
|
|
};
|
|
|
|
statusData["facial_motion"] = new
|
|
{
|
|
client_count = systemController.facialMotion.facialMotionClients?.Count ?? 0
|
|
};
|
|
|
|
statusData["recording"] = new
|
|
{
|
|
is_recording = systemController.IsRecording()
|
|
};
|
|
|
|
statusData["retargeting_remote"] = new
|
|
{
|
|
is_running = systemController.IsRetargetingRemoteRunning(),
|
|
url = systemController.GetRetargetingRemoteUrl()
|
|
};
|
|
}
|
|
|
|
statusData["websocket"] = new
|
|
{
|
|
connected_clients = connectedClients.Count,
|
|
port = port
|
|
};
|
|
|
|
statusData["dashboard"] = new
|
|
{
|
|
enabled = enableDashboard,
|
|
port = dashboardPort,
|
|
urls = dashboardServer?.BoundAddresses?.ToArray() ?? new string[0]
|
|
};
|
|
|
|
SendJson(service, new
|
|
{
|
|
type = "system_status_response",
|
|
timestamp = DateTime.UtcNow.ToString("o"),
|
|
version = "1.0",
|
|
data = statusData
|
|
});
|
|
}
|
|
#endregion
|
|
}
|
|
|
|
public class StreamDeckService : WebSocketBehavior
|
|
{
|
|
private StreamDeckServerManager serverManager;
|
|
|
|
protected override void OnOpen()
|
|
{
|
|
serverManager = StreamDeckServerManager.Instance;
|
|
if (serverManager != null)
|
|
{
|
|
serverManager.OnClientConnected(this);
|
|
}
|
|
Debug.Log("[StreamDeckService] WebSocket 연결 열림");
|
|
}
|
|
|
|
protected override void OnMessage(WebSocketSharp.MessageEventArgs e)
|
|
{
|
|
if (serverManager != null)
|
|
{
|
|
serverManager.ProcessMessageOnMainThread(e.Data, this);
|
|
}
|
|
}
|
|
|
|
public void SendMessage(string message)
|
|
{
|
|
try
|
|
{
|
|
Send(message);
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
Debug.LogError($"[StreamDeckService] 메시지 전송 실패: {ex.Message}");
|
|
}
|
|
}
|
|
|
|
protected override void OnClose(WebSocketSharp.CloseEventArgs e)
|
|
{
|
|
if (serverManager != null)
|
|
{
|
|
serverManager.OnClientDisconnected(this);
|
|
}
|
|
Debug.Log($"[StreamDeckService] WebSocket 연결 닫힘: {e.Reason}");
|
|
}
|
|
|
|
protected override void OnError(WebSocketSharp.ErrorEventArgs e)
|
|
{
|
|
Debug.LogError($"[StreamDeckService] WebSocket 오류: {e.Message}");
|
|
}
|
|
}
|