diff --git a/HttpServer.cs b/HttpServer.cs index 3aaa076..1cffc04 100644 --- a/HttpServer.cs +++ b/HttpServer.cs @@ -27,86 +27,50 @@ namespace DualScreenDemo private static readonly ConcurrentDictionary _fileCache = new ConcurrentDictionary(); private static readonly SemaphoreSlim _requestThrottle = new SemaphoreSlim(20); // 限制并发请求数 private static readonly CancellationTokenSource _serverCts = new CancellationTokenSource(); - - - public static async Task StartServer(string baseDirectory, int port, SongListManager manager) + private static HttpListener _listener; + private static CancellationTokenSource _cts; + private static Task _serverTask; + private static string _baseDirectory = @"themes\superstar\_www"; // 根據實際情況設定 + private static SongListManager _songListManager; + private static TaskCompletionSource _qrReadyTcs; + + public static async Task StartServer(string baseDirectory, int port, SongListManager manager, CancellationToken token) { - songListManager = manager; // 保存传递的SongListManager实例 + _songListManager = manager; string randomFolderName = CreateRandomFolderAndRedirectHTML(baseDirectory); - randomFolderPath = randomFolderName; // 初始化全局变量 - - // 读取 IP 地址 - string localAddress = GetLocalIPAddress(); // 使用获取的本地 IP - string externalAddress = ""; + randomFolderPath = randomFolderName; - // 讀取外網地址 沒有端口號 - string serverAddressFilePath = @"\\SVR01\superstarb\txt\ip.txt"; - if (File.Exists(serverAddressFilePath)) - { - externalAddress = File.ReadAllText(serverAddressFilePath).Trim(); - Console.WriteLine("External address: " + externalAddress); - } - else - { - Console.WriteLine("Warning: External address file not found. Using local address only."); - } - // 創建一個 HttpListener 來監聽 HTTP 請求 - HttpListener listener = new HttpListener(); + string localAddress = GetLocalIPAddress(); + string externalAddress = File.Exists(@"\\SVR01\superstarb\txt\ip.txt") + ? File.ReadAllText(@"\\SVR01\superstarb\txt\ip.txt").Trim() + : ""; - // 構造本地地址的 URL 前綴(包含協議、地址和端口) - string localPrefix = String.Format("http://{0}:{1}/", localAddress, port); - - // 在控制台輸出添加的本地前綴,方便調試 - Console.WriteLine("Adding local prefix: " + localPrefix); - - // 將本地前綴添加到 HttpListener,使其監聽該 URL - listener.Prefixes.Add(localPrefix); - string hostName = System.Net.Dns.GetHostName(); + _listener = new HttpListener(); + _listener.Prefixes.Add($"http://{localAddress}:{port}/"); + string hostName = System.Net.Dns.GetHostName(); string externalPort = '1' + hostName.Substring(Math.Max(2, hostName.Length - 20)); - - // 如果有外网地址,也添加外网地址前缀 if (!string.IsNullOrEmpty(externalAddress)) { - string[] parts = externalAddress.Split(':'); - string host = parts[0]; - - //int externalPort = parts.Length > 1 ? int.Parse(parts[1]) : port; - - string externalPrefix = String.Format("http://{0}:{1}/", host, externalPort); - Console.WriteLine("Adding external prefix: " + externalPrefix); - - try - { - listener.Prefixes.Add(externalPrefix); - } - catch (Exception ex) - { - Console.WriteLine($"Warning: Could not add external prefix: {ex.Message}"); - } + string host = externalAddress.Split(':')[0]; + externalPort = '1' + System.Net.Dns.GetHostName().Substring(Math.Max(2, System.Net.Dns.GetHostName().Length - 20)); + _listener.Prefixes.Add($"http://{host}:{externalPort}/"); } - - // 生成两个二维码内容 string localQrContent = String.Format("http://{0}:{1}/{2}/windows.html", localAddress, port, randomFolderName); - // string localQrContent = String.Format("http://{0}:{1}/{2}/windows.html", "ss.net.dnsnet.cc", 1102, randomFolderName); + // 修改外网二维码内容生成 string externalQrContent = !string.IsNullOrEmpty(externalAddress) ? String.Format("http://{0}:{1}/{2}/windows.html", externalAddress, externalPort, randomFolderName) : localQrContent; - - Console.WriteLine("local QR Content : " + localQrContent); - Console.WriteLine("external QR Content : " + externalQrContent); - // 生成二维码(这里使用外网地址的二维码,因为通常外网地址更有用) - string qrImagePath = GenerateQRCode(externalQrContent, Path.Combine(baseDirectory, randomFolderName, "qrcode.png")); + GenerateQRCode(externalQrContent, Path.Combine(baseDirectory, randomFolderName, "qrcode.png")); + + _qrReadyTcs?.TrySetResult(randomFolderName); // safe call,null-safe try { - listener.Start(); + _listener.Start(); Console.WriteLine("Server started."); - - // 在程序关闭时删除随机文件夹 - AppDomain.CurrentDomain.ProcessExit += (s, e) => DeleteRandomFolder(baseDirectory); } catch (HttpListenerException ex) { @@ -114,11 +78,51 @@ namespace DualScreenDemo return; } - while (true) + while (!token.IsCancellationRequested) { - HttpListenerContext context = await listener.GetContextAsync(); - await ProcessRequestAsync(context, baseDirectory, randomFolderName); + try + { + HttpListenerContext context = await _listener.GetContextAsync(); + await ProcessRequestAsync(context, baseDirectory, randomFolderName); + } + catch (HttpListenerException) + { + // 可能是 Stop() 引起,不處理 + break; + } } + + _listener.Close(); + Console.WriteLine("Server stopped."); + } + + public static async Task RestartServer() + { + Console.WriteLine("Restarting server..."); + + _cts?.Cancel(); + _listener?.Stop(); + + if (_serverTask != null) + { + try { await _serverTask; } + catch (OperationCanceledException) { } + } + + DeleteRandomFolder(_baseDirectory); + + _cts = new CancellationTokenSource(); + + + // ✅ 等 QR code 跑完 + _qrReadyTcs = new TaskCompletionSource(); + _serverTask = StartServer(_baseDirectory, _port, _songListManager, _cts.Token); + string readyFolder = await _qrReadyTcs.Task; + + // ✅ 等 QR Code 圖片準備好之後再處理 UI 更新 + Console.WriteLine("restart : " + readyFolder); + PrimaryForm.Instance.OverlayQRCodeOnImage(readyFolder); + OverlayForm.Instance.DisplayQRCodeOnOverlay(readyFolder); } private static async Task ProcessRequestWithTimeout(HttpListenerContext context, string baseDirectory, string randomFolderName) @@ -161,11 +165,11 @@ namespace DualScreenDemo try { Directory.Delete(fullPath, true); - Console.WriteLine("Deleted random folder: " + fullPath); + Console.WriteLine("刪除 random folder: " + fullPath); } catch (Exception ex) { - Console.WriteLine("Error deleting random folder: " + ex.Message); + Console.WriteLine("錯誤 deleting random folder: " + ex.Message); } } } @@ -173,7 +177,6 @@ namespace DualScreenDemo public static string GetServerAddress() { return String.Format("http://{0}:{1}/", _localIP, _port); - // return String.Format("http://111.246.145.170:8080/"); } /// /// 生成隨機路徑 diff --git a/HttpServerManager.cs b/HttpServerManager.cs index b0c4e37..f490bcb 100644 --- a/HttpServerManager.cs +++ b/HttpServerManager.cs @@ -4,13 +4,17 @@ namespace DualScreenDemo { public static class HttpServerManager { + private static CancellationTokenSource _cts; + public static async void StartServer() { int httpPort = 9090; // 你可以修改此端口 string baseDirectory = Path.Combine(Application.StartupPath, @"themes\superstar\_www"); CleanUpDirectory(baseDirectory); - await HttpServer.StartServer(baseDirectory, httpPort, Program.songListManager); + // await HttpServer.StartServer(baseDirectory, httpPort, Program.songListManager); + _cts = new CancellationTokenSource(); + await HttpServer.StartServer(baseDirectory, httpPort, Program.songListManager, _cts.Token); } diff --git a/OverlayFormObj/OverlayForm.cs b/OverlayFormObj/OverlayForm.cs index 45946e5..50ab165 100644 --- a/OverlayFormObj/OverlayForm.cs +++ b/OverlayFormObj/OverlayForm.cs @@ -62,11 +62,12 @@ namespace OverlayFormObj get { return _mainForm; } private set { _mainForm = value; } } - + public static OverlayForm Instance { get; private set; } public OverlayForm() { SetStyle(ControlStyles.SupportsTransparentBackColor, true); MainForm = this; + Instance = this; InitializeFormSettings(); ConfigureTimers(); InitializeLabels(); @@ -1512,7 +1513,6 @@ private void DisplayArtists(List artists, int page)//歌星點進去後 ClearDisplay(); DisplaySongs(currentPage); } - /* 秒數偵測BUG 測試 */ public void AddSongToPlaylist(SongData songData) { try diff --git a/PrimaryFormParts/HotSong/PrimaryForm.HotSong.cs b/PrimaryFormParts/HotSong/PrimaryForm.HotSong.cs index aaaf740..c1debf7 100644 --- a/PrimaryFormParts/HotSong/PrimaryForm.HotSong.cs +++ b/PrimaryFormParts/HotSong/PrimaryForm.HotSong.cs @@ -66,7 +66,7 @@ namespace DualScreenDemo string query = $"SELECT * FROM song_library_cache WHERE language_name = '國語' ORDER BY `song_id` DESC LIMIT {songLimit}"; var guoYuSongs = SearchSongs_Mysql(query); UpdateSongList(guoYuSongs); - + SetButtonsVisibility(); HideQRCode(); } @@ -84,7 +84,6 @@ namespace DualScreenDemo myFavoritesButton.BackgroundImage = myFavoritesNormalBackground; promotionsButton.BackgroundImage = promotionsNormalBackground; deliciousFoodButton.BackgroundImage = deliciousFoodNormalBackground; - activeButton.BackgroundImage = activeBackground; } @@ -144,13 +143,8 @@ namespace DualScreenDemo private void HideQRCode() { - if (pictureBoxQRCode != null) - { - pictureBoxQRCode.Visible = false; - } - if(closeQRCodeButton != null){ - closeQRCodeButton.Visible = false; - } + pictureBoxQRCode.Visible = false; + closeQRCodeButton.Visible = false; } private void InitializeButtonsForHotSong() diff --git a/PrimaryFormParts/PrimaryForm.QRCode.cs b/PrimaryFormParts/PrimaryForm.QRCode.cs index d7e1426..dc46db5 100644 --- a/PrimaryFormParts/PrimaryForm.QRCode.cs +++ b/PrimaryFormParts/PrimaryForm.QRCode.cs @@ -3,10 +3,10 @@ namespace DualScreenDemo { public partial class PrimaryForm : Form { - private PictureBox pictureBoxQRCode; - private Button closeQRCodeButton; + public PictureBox pictureBoxQRCode; + public Button closeQRCodeButton; - private void OverlayQRCodeOnImage(string randomFolderPath) + public void OverlayQRCodeOnImage(string randomFolderPath) { try { @@ -40,17 +40,19 @@ namespace DualScreenDemo string qrImagePath = Path.Combine(Application.StartupPath, "themes/superstar/_www", randomFolderPath, "qrcode.png"); if (!File.Exists(qrImagePath)) { - Console.WriteLine("QR code image not found: " + qrImagePath); + Console.WriteLine("布局 image not found: " + qrImagePath); return; } - + string tempPath = Path.GetTempFileName(); + File.Copy(qrImagePath, tempPath, true); // 複製一份再讀,避開快取 + Image qrCodeImage = null; for (int i = 0; i < 3; i++) { try { - using (var fs = new FileStream(qrImagePath, FileMode.Open, FileAccess.Read)) + using (var fs = new FileStream(tempPath, FileMode.Open, FileAccess.Read, FileShare.ReadWrite)) { qrCodeImage = Image.FromStream(fs); } @@ -59,9 +61,10 @@ namespace DualScreenDemo catch (Exception ex) { Console.WriteLine("Error loading QR code image: " + ex.Message); - System.Threading.Thread.Sleep(100); + Thread.Sleep(100); } } + File.Delete(tempPath); // 用完記得刪除 if (qrCodeImage == null) { @@ -78,15 +81,10 @@ namespace DualScreenDemo { g.DrawImage(baseImage, 0, 0); - - - Rectangle qrCodeRect = new Rectangle(32, 39, 165, 165); g.DrawImage(qrCodeImage, qrCodeRect); - } - - + } pictureBoxQRCode.Image = new Bitmap(bitmap); } } diff --git a/PrimaryFormParts/PrimaryForm.cs b/PrimaryFormParts/PrimaryForm.cs index f5b0f0a..3c55174 100644 --- a/PrimaryFormParts/PrimaryForm.cs +++ b/PrimaryFormParts/PrimaryForm.cs @@ -339,10 +339,14 @@ namespace DualScreenDemo buttonTopLeft.BringToFront(); buttonThanks.BringToFront(); } - + // 修正螢幕初始化關鍵 public void HideSendOffScreen() { sendOffPanel.Visible = false; + foreach (Control ctrl in this.Controls) + { + ctrl.Enabled = true; + } } private void HideControlsRecursively(Control parent) @@ -2457,12 +2461,48 @@ namespace DualScreenDemo // 添加 Form Load 事件處理方法 private void PrimaryForm_Load(object sender, EventArgs e) - { - // 確保所有控件都已初始化完成後,再觸發熱門排行按鈕點擊 + { if (hotPlayButton != null) { HotPlayButton_Click(null, EventArgs.Empty); } + try + { + string stateFilePath = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "txt", "states.txt"); + string initialState = ReadStateFile(stateFilePath); + + if (initialState.Equals("CLOSE", StringComparison.OrdinalIgnoreCase)) + { + foreach (Control ctrl in this.Controls) + { + ctrl.Enabled = false; + } + + ShowSendOffScreen(); + } + } + catch (Exception ex) + { + Console.WriteLine($"[PrimaryForm_Load] 初始化狀態錯誤: {ex.Message}"); + } + // 確保所有控件都已初始化完成後,再觸發熱門排行按鈕點擊 + + } + + private string ReadStateFile(string filePath) + { + try + { + if (File.Exists(filePath)) + { + return File.ReadAllText(filePath).Trim(); + } + } + catch (Exception ex) + { + Console.WriteLine("讀取 states.txt 時發生錯誤: " + ex.Message); + } + return ""; } private void AutoRefreshTimer_Tick(object sender, EventArgs e) diff --git a/TCPServer.cs b/TCPServer.cs index 641b5be..613a9ba 100644 --- a/TCPServer.cs +++ b/TCPServer.cs @@ -87,9 +87,10 @@ namespace DualScreenDemo //sender.Start(); // Console.WriteLine($"heart beat for server{sender.RemoteEndPoint.Address}:{sender.RemoteEndPoint.Port}"); try { + string stateFilePath = Path.Combine(AppDomain.CurrentDomain.BaseDirectory,"txt","states.txt"); string initialState = ReadStateFile(stateFilePath); - + /* if (initialState.Equals("CLOSE", StringComparison.OrdinalIgnoreCase)) { _ = SafeInvoke(PrimaryForm.Instance, () => @@ -106,6 +107,7 @@ namespace DualScreenDemo } }); } + */ while (true) { @@ -139,11 +141,13 @@ namespace DualScreenDemo if (command.Trim().Equals("X", StringComparison.OrdinalIgnoreCase)) { + PrimaryForm.Instance.pictureBoxQRCode.Visible = false; + PrimaryForm.Instance.closeQRCodeButton.Visible = false; _ = SafeInvoke(VideoPlayerForm.Instance, async () => { if (IsFormReady(PrimaryForm.Instance)) { - await SafeInvoke(PrimaryForm.Instance, () => + await SafeInvoke(PrimaryForm.Instance, async () => { PrimaryForm.Instance.ShowSendOffScreen(); @@ -178,7 +182,14 @@ namespace DualScreenDemo VideoPlayerForm.Instance.PlayNextSong(); PrimaryForm.Instance.logout(); Console.WriteLine("已設置新的播放列表,包含當前歌曲和 CLOSE.MPG"); - + try + { + await HttpServer.RestartServer(); + } + catch (Exception ex) + { + Console.WriteLine("RestartServer 發生錯誤: " + ex.Message); + } } else {