diff --git a/HttpServer.cs b/HttpServer.cs index 4b137c4..4f11857 100644 --- a/HttpServer.cs +++ b/HttpServer.cs @@ -35,8 +35,14 @@ namespace DualScreenDemo private static readonly ConcurrentDictionary _fileCache = new ConcurrentDictionary(); private static readonly SemaphoreSlim _requestThrottle = new SemaphoreSlim(20); // 限制并发请求数 private static readonly CancellationTokenSource _serverCts = new CancellationTokenSource(); + 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) { songListManager = manager; // 保存传递的SongListManager实例 @@ -130,6 +136,101 @@ namespace DualScreenDemo await ProcessRequestAsync(context, baseDirectory, randomFolderName); } } + */ + public static async Task StartServer(string baseDirectory, int port, SongListManager manager, CancellationToken token) + { + _songListManager = manager; + string randomFolderName = CreateRandomFolderAndRedirectHTML(baseDirectory); + randomFolderPath = randomFolderName; + Console.WriteLine("測試" + randomFolderPath); + string localAddress = GetLocalIPAddress(); + string externalAddress = File.Exists(@"\\SVR01\superstarb\txt\ip.txt") + ? File.ReadAllText(@"\\SVR01\superstarb\txt\ip.txt").Trim() + : ""; + + _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 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 externalQrContent = !string.IsNullOrEmpty(externalAddress) ? + String.Format("http://{0}:{1}/{2}/windows.html", externalAddress, externalPort, randomFolderName) : + localQrContent; + + /*string qrContent = !string.IsNullOrEmpty(externalAddress) + ? $"http://{externalAddress}:{port}/{randomFolderName}/windows.html" + : $"http://{localAddress}:{port}/{randomFolderName}/windows.html";*/ + Console.WriteLine("qrcode 測試內容 " + externalQrContent); + GenerateQRCode(externalQrContent, Path.Combine(baseDirectory, randomFolderName, "qrcode.png")); + + _qrReadyTcs?.TrySetResult(randomFolderName); // safe call,null-safe + try + { + _listener.Start(); + Console.WriteLine("Server started."); + } + catch (HttpListenerException ex) + { + Console.WriteLine("Error starting server: " + ex.Message); + return; + } + + while (!token.IsCancellationRequested) + { + 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) { diff --git a/HttpServerManager.cs b/HttpServerManager.cs index b77c7fe..6436545 100644 --- a/HttpServerManager.cs +++ b/HttpServerManager.cs @@ -1,18 +1,21 @@ using System.IO; using System.Linq; using System.Windows.Forms; - +using System.Threading; 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 0a2342a..dc29afe 100644 --- a/OverlayFormObj/OverlayForm.cs +++ b/OverlayFormObj/OverlayForm.cs @@ -74,11 +74,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; + MainForm = this; + Instance = this; InitializeFormSettings(); ConfigureTimers(); diff --git a/PrimaryFormParts/PrimaryForm.QRCode.cs b/PrimaryFormParts/PrimaryForm.QRCode.cs index 9c5287c..05da2b0 100644 --- a/PrimaryFormParts/PrimaryForm.QRCode.cs +++ b/PrimaryFormParts/PrimaryForm.QRCode.cs @@ -7,10 +7,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 { @@ -48,13 +48,15 @@ namespace DualScreenDemo 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); } @@ -66,7 +68,7 @@ namespace DualScreenDemo System.Threading.Thread.Sleep(100); } } - + File.Delete(tempPath); // 用完記得刪除 if (qrCodeImage == null) { Console.WriteLine("Failed to load QR code image after multiple attempts."); diff --git a/TCPServer.cs b/TCPServer.cs index 4837bb3..c443d89 100644 --- a/TCPServer.cs +++ b/TCPServer.cs @@ -149,11 +149,13 @@ namespace DualScreenDemo // 指令為 "X":播放 CLOSE.MPG,並顯示送客畫面 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(); PrimaryForm.Instance.HideFireScreen(); @@ -167,13 +169,13 @@ namespace DualScreenDemo { // 建立結束播放用的 SongData 實例 SongData closeSong = new SongData( - "", "", "結束播放", 0, "", "", "", "", - DateTime.Now, closePath, "", "", "", "", + "", "", "結束播放", 0, "", "", "", "", + DateTime.Now, closePath, "", "", "", "", "", "", "", "", "", "", "", 1 ); // 建立新的播放清單 - + VideoPlayerForm.publicPlaylist = new List(); VideoPlayerForm.playingSongList = new List(); PrimaryForm.playedSongsHistory = new List(); @@ -182,8 +184,8 @@ namespace DualScreenDemo { VideoPlayerForm.playingSongList.Add(VideoPlayerForm.Instance.currentPlayingSong); } - - VideoPlayerForm.publicPlaylist.Add(closeSong); + + VideoPlayerForm.publicPlaylist.Add(closeSong); // 將 CLOSE.MPG 加入播放清單 VideoPlayerForm.playingSongList.Add(closeSong); @@ -195,8 +197,16 @@ namespace DualScreenDemo { OverlayForm.MainForm.nextSongLabel.Visible = false; } - VideoPlayerForm.Instance.PlayNextSong(); + VideoPlayerForm.Instance.PlayNextSong(); Console.WriteLine("已設置新的播放列表,包含當前歌曲和 CLOSE.MPG"); + try + { + await HttpServer.RestartServer(); + } + catch (Exception ex) + { + Console.WriteLine("RestartServer 發生錯誤: " + ex.Message); + } } else { diff --git a/bin/secondary_graph.grf b/bin/secondary_graph.grf index c306648..b6cc34f 100644 --- a/bin/secondary_graph.grf +++ b/bin/secondary_graph.grf @@ -14,5 +14,5 @@ Filter: LAV Splitter Pin: Input Pin: Video Pin: Audio -Filter: C:\video\BGM01.mpg +Filter: C:\video\welcome.mpg Pin: Output