diff --git a/TCPServer.cs b/TCPServer.cs index 9cccaf2..ea3fa80 100644 --- a/TCPServer.cs +++ b/TCPServer.cs @@ -1,20 +1,25 @@ +using System; using System.Net; -using System.Net.Sockets; +using System.Net.Sockets; using System.Text; using System.Text.RegularExpressions; -using System.IO; // 為 Path 和 File 提供支持 +using ActionString = System.Action; // 现在可以在代码中使用 ActionString +using System.Threading.Tasks; +using System.Windows.Forms; +using System.Drawing; using DBObj; using OverlayFormObj; + namespace DualScreenDemo { public class TCPServer { private TcpListener listener; - private const int Port = 1000; + private const int Port = 1000; private readonly string hostNameSuffix; - private Room _room; - //private bool isProcessingCommand = false; - + private readonly Room _room; + public static event ActionString OnDisplayBarrage; + public static event ActionString OnDisplaySticker; public TCPServer(Room room) { @@ -25,29 +30,334 @@ namespace DualScreenDemo _ = Task.Run(() => Start()); } + // ================================ + // 啟動 TCP Server + // ================================ + public void Start() + { + listener.Start(); + Console.WriteLine($"Server started on port {Port}."); + + _ = Task.Run(async () => + { + try + { + while (true) + { + Console.WriteLine("Waiting for connections..."); + TcpClient client = await listener.AcceptTcpClientAsync(); + Console.WriteLine("Client connected!"); + + _ = Task.Run(() => HandleClient(client)); + } + } + catch (Exception ex) + { + Console.WriteLine($"[Server Error] {ex.Message}"); + } + finally + { + listener.Stop(); + } + }); + } + + // ================================ + // 處理 Client 連線 + // ================================ + private async Task HandleClient(TcpClient client) + { + using (client) + { + try + { + NetworkStream stream = client.GetStream(); + byte[] buffer = new byte[1024]; + + while (client.Connected) + { + int bytesRead = await stream.ReadAsync(buffer, 0, buffer.Length); + if (bytesRead == 0) break; + + string request = Encoding.UTF8.GetString(buffer, 0, bytesRead).Trim(); + Console.WriteLine("Received: " + request); + + // 先判斷是否是 Chat 指令 (格式帶有 ",") + if (request.Contains(",")) + await HandleChatCommand(request, stream); + else if (request.Length >= 5) + { + string requestHostSuffix = request.Substring(0, 3); + string command = request.Substring(4); + + if (requestHostSuffix.Equals(hostNameSuffix, StringComparison.OrdinalIgnoreCase)) + await HandleRoomCommand(command, stream); + else + await HandleOverlayCommand(request, stream); + } + } + } + catch (Exception ex) + { + Console.WriteLine($"[Client Error] {ex.Message}"); + } + } + + Console.WriteLine("Client disconnected."); + } + + // ================================ + // 房間控制指令 + // ================================ + private async Task HandleRoomCommand(string command, NetworkStream stream) + { + switch (command.ToLowerInvariant()) + { + case "X": + _ = SafeInvoke(VideoPlayerForm.Instance, async () => + { + _room.set("closed"); + try { await HttpServer.RestartServer(); } + catch (Exception ex) { Console.WriteLine($"RestartServer Error: {ex.Message}"); } + }); + break; + + case "O": + _room.set("active"); + break; + + case "F": + _ = SafeInvoke(PrimaryForm.Instance, () => _room.set("fire")); + break; + + case "pause": + TogglePause(); + break; + + case "volume_up": + OverlayForm.MainForm.Invoke(() => OverlayForm.MainForm.ShowTopRightLabelTime("音量 ↑", "a2 b3 a4")); + break; + + case "volume_down": + OverlayForm.MainForm.Invoke(() => OverlayForm.MainForm.ShowTopRightLabelTime("音量 ↓", "a2 b4 a4")); + break; + + case "mic_up": + OverlayForm.MainForm.Invoke(() => OverlayForm.MainForm.ShowTopRightLabelTime("麥克風 ↑", "a2 b5 a4")); + break; + + case "mic_down": + OverlayForm.MainForm.Invoke(() => OverlayForm.MainForm.ShowTopRightLabelTime("麥克風 ↓", "a2 b6 a4")); + break; + + case "mute": + ToggleMute(); + break; + + case "original_song": + PrimaryForm.Instance.Invoke(() => PrimaryForm.Instance.videoPlayerForm.ToggleVocalRemoval()); + break; + + case "service": + OverlayForm.MainForm.Invoke(() => OverlayForm.MainForm.ShowTopRightLabel("服務鈴", "a2 53 a4")); + break; + + case "replay": + PrimaryForm.Instance.Invoke(() => PrimaryForm.Instance.videoPlayerForm.ReplayCurrentSong()); + break; + + case "male_key": + SendSerialCommand(new byte[] { 0xA2, 0x7F, 0xA4 }, new byte[] { 0xA2, 0xB2, 0xA4 }, 2); + break; + + case "female_key": + SendSerialCommand(new byte[] { 0xA2, 0x7F, 0xA4 }, new byte[] { 0xA2, 0xB1, 0xA4 }, 2); + break; + + case "cut": + PrimaryForm.Instance.Invoke(() => PrimaryForm.Instance.videoPlayerForm.PlayNextSong()); + break; + + case "lower_key": + SendSerialCommand(new byte[] { 0xA2, 0xB2, 0xA4 }); + break; + + case "standard_key": + SendSerialCommand(new byte[] { 0xA2, 0x7F, 0xA4 }); + break; + + case "raise_key": + SendSerialCommand(new byte[] { 0xA2, 0xB1, 0xA4 }); + break; + + default: + Console.WriteLine($"[Unknown Command] {command}"); + break; + } + + await SendOkResponse(stream); + } + + // ================================ + // 跑馬燈/公告指令 + // ================================ + private async Task HandleOverlayCommand(string message, NetworkStream stream) + { + if (!IsFormReady(OverlayForm.MainForm)) return; + + Match match = Regex.Match(message, @"^(全部|\d{4})\((白色|紅色|綠色|黑色|藍色)\)-"); + _ = SafeInvoke(OverlayForm.MainForm, () => + { + if (match.Success) + { + string marqueeMessage = message.Substring(match.Value.Length).Trim(); + Color textColor = GetColorFromString(match.Groups[2].Value); + OverlayForm.MainForm.UpdateMarqueeText(marqueeMessage, OverlayForm.MarqueeStartPosition.Right, textColor); + } + else + { + OverlayForm.MainForm.UpdateMarqueeTextSecondLine("系統公告: "); + } + }); + + await SendOkResponse(stream); + } + + // ================================ + // Chat 指令 (send-sticker, message) + // ================================ + private async Task HandleChatCommand(string message, NetworkStream stream) + { + try + { + string[] parts = message.Split(',', 2); // 只切一次 + if (parts.Length < 2) + { + Console.WriteLine($"[ChatCommand Error] 格式錯誤: {message}"); + return; + } + + string cmd = parts[0].Trim().ToLowerInvariant(); + string content = parts[1].Trim(); + + switch (cmd) + { + case "send-sticker": + _ = SafeInvoke(OverlayForm.MainForm, () => OnDisplaySticker?.Invoke(content)); + break; + + case "message": + _ = SafeInvoke(OverlayForm.MainForm, () => OnDisplayBarrage?.Invoke(content) ); + break; + + default: + Console.WriteLine($"[Unknown ChatCommand] {cmd}"); + break; + } + + await SendOkResponse(stream); + } + catch (Exception ex) + { + Console.WriteLine($"[ChatCommand Error] {ex.Message}"); + } + } + + // ================================ + // 輔助方法 + // ================================ + private async Task SendOkResponse(NetworkStream stream) + { + byte[] okResponse = Encoding.UTF8.GetBytes("OK\n"); + await stream.WriteAsync(okResponse, 0, okResponse.Length); + } + + private void TogglePause() + { + PrimaryForm.Instance.Invoke(() => + { + if (VideoPlayerForm.Instance.isPaused) + { + VideoPlayerForm.Instance.Play(); + PrimaryForm.Instance.pauseButton.Visible = true; + PrimaryForm.Instance.playButton.Visible = false; + PrimaryForm.Instance.syncPauseButton.Visible = true; + PrimaryForm.Instance.syncPlayButton.Visible = false; + } + else + { + VideoPlayerForm.Instance.Pause(); + PrimaryForm.Instance.pauseButton.Visible = false; + PrimaryForm.Instance.playButton.Visible = true; + PrimaryForm.Instance.syncPauseButton.Visible = false; + PrimaryForm.Instance.syncPlayButton.Visible = true; + } + }); + } + + private void ToggleMute() + { + PrimaryForm.Instance.Invoke(() => + { + if (PrimaryForm.Instance.videoPlayerForm.isMuted) + { + PrimaryForm.Instance.videoPlayerForm.Mute(false); + PrimaryForm.Instance.videoPlayerForm.isMuted = false; + OverlayForm.MainForm.HideMuteLabel(); + } + else + { + PrimaryForm.Instance.videoPlayerForm.Mute(true); + PrimaryForm.Instance.videoPlayerForm.isMuted = true; + OverlayForm.MainForm.ShowMuteLabel(); + } + }); + } + + private void SendSerialCommand(byte[] prefix, byte[]? repeat = null, int repeatCount = 1) + { + if (SerialPortManager.mySerialPort == null || !SerialPortManager.mySerialPort.IsOpen) + { + Console.WriteLine("[SerialPort] Port not open."); + return; + } + + SerialPortManager.mySerialPort.Write(prefix, 0, prefix.Length); + + if (repeat != null) + { + for (int i = 0; i < repeatCount; i++) + SerialPortManager.mySerialPort.Write(repeat, 0, repeat.Length); + } + } + + private Color GetColorFromString(string colorName) => colorName switch + { + "白色" => Color.White, + "紅色" => Color.Red, + "綠色" => Color.LightGreen, + "黑色" => Color.Black, + "藍色" => Color.LightBlue, + _ => Color.Black + }; + + private string GetHostNameSuffix() + { + string hostName = Dns.GetHostName(); + return hostName.Length > 3 ? hostName[^3..] : hostName; + } + + // ================================ + // 表單安全呼叫 + // ================================ private bool IsFormReady(Form form) { if (form == null) return false; - bool isReady = false; try { - if (form.IsHandleCreated && !form.IsDisposed) - { - if (form.InvokeRequired) - { - form.Invoke(new Action(() => isReady = true)); - } - else - { - isReady = true; - } - } + return form.IsHandleCreated && !form.IsDisposed; } - catch - { - isReady = false; - } - return isReady; + catch { return false; } } private async Task SafeInvoke(Form form, Action action, int maxRetries = 10) @@ -60,14 +370,8 @@ namespace DualScreenDemo { if (IsFormReady(form)) { - if (form.InvokeRequired) - { - form.Invoke(action); - } - else - { - action(); - } + if (form.InvokeRequired) form.Invoke(action); + else action(); return; } } @@ -76,157 +380,10 @@ namespace DualScreenDemo Console.WriteLine($"Invoke attempt {i + 1} failed: {ex.Message}"); } - await Task.Delay(500); // 等待500毫秒后重试 + await Task.Delay(500); } + Console.WriteLine("Failed to invoke action after maximum retries"); } - - public void Start() - { - listener.Start(); - Console.WriteLine("Server started on port " + Port + "."); - - try { - - while (true) - { - Console.WriteLine("Waiting for connections..."); - using (TcpClient client = listener.AcceptTcpClient()) - { - Console.WriteLine("Connected!"); - NetworkStream stream = client.GetStream(); - - while (client.Connected) - { - byte[] buffer = new byte[1024]; - int bytesRead = stream.Read(buffer, 0, buffer.Length); - if (bytesRead == 0) - break; - - string request = Encoding.UTF8.GetString(buffer, 0, bytesRead); - Console.WriteLine("Received: " + request.Trim()); - - if (request.Length < 5) - { - continue; - } - - string requestHostSuffix = request.Substring(0, 3); - string command = request.Substring(4); - - if (requestHostSuffix.Equals(hostNameSuffix, StringComparison.OrdinalIgnoreCase)) - { - // 新增監聽指令 - - if (command.Trim().Equals("X", StringComparison.OrdinalIgnoreCase)) - { - _ = SafeInvoke(VideoPlayerForm.Instance, async () => - { - _room.set("closed"); - try{ - await HttpServer.RestartServer(); - } catch (Exception ex) { - Console.WriteLine("RestartServer 發生錯誤: " + ex.Message); - } - }); - - - byte[] okResponse = Encoding.UTF8.GetBytes("OK\n"); - stream.Write(okResponse, 0, okResponse.Length); - - continue; - } - - if (command.Trim().Equals("O", StringComparison.OrdinalIgnoreCase)) - { - // 開台時跳至首頁 - _room.set("active"); - byte[] okResponse = Encoding.UTF8.GetBytes("OK\n"); - stream.Write(okResponse, 0, okResponse.Length); - continue; - } - if (command.Trim().Equals("F", StringComparison.OrdinalIgnoreCase)) - { - _ = SafeInvoke(PrimaryForm.Instance, () => - { - _room.set("fire"); - }); - byte[] okResponse = Encoding.UTF8.GetBytes("OK\n"); - stream.Write(okResponse, 0, okResponse.Length); - continue; - } - - - } - - if (IsFormReady(OverlayForm.MainForm)) - { - string message = request.Trim(); - string pattern = @"^(全部|\d{4})\((白色|紅色|綠色|黑色|藍色)\)-"; - Match match = Regex.Match(message, pattern); - _ = SafeInvoke(OverlayForm.MainForm, () => - { - if (match.Success) - { - string marqueeMessage = message.Substring(match.Value.Length).Trim(); - Color textColor = GetColorFromString(match.Groups[2].Value); - OverlayForm.MainForm.UpdateMarqueeText(marqueeMessage, OverlayForm.MarqueeStartPosition.Right, textColor); - } - else - { - string marqueeMessage = "系統公告: " ; - OverlayForm.MainForm.UpdateMarqueeTextSecondLine(marqueeMessage); - } - }); - byte[] okResponse = Encoding.UTF8.GetBytes("OK\n"); - stream.Write(okResponse, 0, okResponse.Length); - } - - /* - if (request.Trim().Equals("exit", StringComparison.OrdinalIgnoreCase)) - { - break; - } - */ - } - - //Console.WriteLine("Connection closed."); - } - } - } - catch (Exception e) - { - Console.WriteLine($"Error: {e.Message}"); - } - finally - { - listener.Stop(); - } - } - - private Color GetColorFromString(string colorName) - { - switch (colorName) - { - case "白色": - return Color.White; - case "紅色": - return Color.Red; - case "綠色": - return Color.LightGreen; - case "黑色": - return Color.Black; - case "藍色": - return Color.LightBlue; - default: - return Color.Black; - } - } - - private string GetHostNameSuffix() - { - string hostName = Dns.GetHostName(); - return hostName.Length > 3 ? hostName.Substring(hostName.Length - 3) : hostName; - } } } \ No newline at end of file