自動刷新QRCODE,修正開關台元件問題

This commit is contained in:
jasonchenwork 2025-05-19 13:23:09 +08:00
parent 2dda501294
commit 706daa0080
7 changed files with 148 additions and 98 deletions

View File

@ -27,67 +27,36 @@ namespace DualScreenDemo
private static readonly ConcurrentDictionary<string, byte[]> _fileCache = new ConcurrentDictionary<string, byte[]>(); private static readonly ConcurrentDictionary<string, byte[]> _fileCache = new ConcurrentDictionary<string, byte[]>();
private static readonly SemaphoreSlim _requestThrottle = new SemaphoreSlim(20); // 限制并发请求数 private static readonly SemaphoreSlim _requestThrottle = new SemaphoreSlim(20); // 限制并发请求数
private static readonly CancellationTokenSource _serverCts = new CancellationTokenSource(); 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<string> _qrReadyTcs;
public static async Task StartServer(string baseDirectory, int port, SongListManager manager, CancellationToken token)
public static async Task StartServer(string baseDirectory, int port, SongListManager manager)
{ {
songListManager = manager; // 保存传递的SongListManager实例 _songListManager = manager;
string randomFolderName = CreateRandomFolderAndRedirectHTML(baseDirectory); string randomFolderName = CreateRandomFolderAndRedirectHTML(baseDirectory);
randomFolderPath = randomFolderName; // 初始化全局变量 randomFolderPath = randomFolderName;
// 读取 IP 地址 string localAddress = GetLocalIPAddress();
string localAddress = GetLocalIPAddress(); // 使用获取的本地 IP string externalAddress = File.Exists(@"\\SVR01\superstarb\txt\ip.txt")
string externalAddress = ""; ? File.ReadAllText(@"\\SVR01\superstarb\txt\ip.txt").Trim()
: "";
// 讀取外網地址 沒有端口號 _listener = new HttpListener();
string serverAddressFilePath = @"\\SVR01\superstarb\txt\ip.txt"; _listener.Prefixes.Add($"http://{localAddress}:{port}/");
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();
// 構造本地地址的 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(); string hostName = System.Net.Dns.GetHostName();
string externalPort = '1' + hostName.Substring(Math.Max(2, hostName.Length - 20)); string externalPort = '1' + hostName.Substring(Math.Max(2, hostName.Length - 20));
// 如果有外网地址,也添加外网地址前缀
if (!string.IsNullOrEmpty(externalAddress)) if (!string.IsNullOrEmpty(externalAddress))
{ {
string[] parts = externalAddress.Split(':'); string host = externalAddress.Split(':')[0];
string host = parts[0]; externalPort = '1' + System.Net.Dns.GetHostName().Substring(Math.Max(2, System.Net.Dns.GetHostName().Length - 20));
_listener.Prefixes.Add($"http://{host}:{externalPort}/");
//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 localQrContent = String.Format("http://{0}:{1}/{2}/windows.html", localAddress, port, randomFolderName); 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);
// 修改外网二维码内容生成 // 修改外网二维码内容生成
@ -95,18 +64,13 @@ namespace DualScreenDemo
String.Format("http://{0}:{1}/{2}/windows.html", externalAddress, externalPort, randomFolderName) : String.Format("http://{0}:{1}/{2}/windows.html", externalAddress, externalPort, randomFolderName) :
localQrContent; localQrContent;
Console.WriteLine("local QR Content : " + localQrContent); GenerateQRCode(externalQrContent, Path.Combine(baseDirectory, randomFolderName, "qrcode.png"));
Console.WriteLine("external QR Content : " + externalQrContent);
// 生成二维码(这里使用外网地址的二维码,因为通常外网地址更有用)
string qrImagePath = GenerateQRCode(externalQrContent, Path.Combine(baseDirectory, randomFolderName, "qrcode.png"));
_qrReadyTcs?.TrySetResult(randomFolderName); // safe callnull-safe
try try
{ {
listener.Start(); _listener.Start();
Console.WriteLine("Server started."); Console.WriteLine("Server started.");
// 在程序关闭时删除随机文件夹
AppDomain.CurrentDomain.ProcessExit += (s, e) => DeleteRandomFolder(baseDirectory);
} }
catch (HttpListenerException ex) catch (HttpListenerException ex)
{ {
@ -114,11 +78,51 @@ namespace DualScreenDemo
return; return;
} }
while (true) while (!token.IsCancellationRequested)
{ {
HttpListenerContext context = await listener.GetContextAsync(); try
await ProcessRequestAsync(context, baseDirectory, randomFolderName); {
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<string>();
_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) private static async Task ProcessRequestWithTimeout(HttpListenerContext context, string baseDirectory, string randomFolderName)
@ -161,11 +165,11 @@ namespace DualScreenDemo
try try
{ {
Directory.Delete(fullPath, true); Directory.Delete(fullPath, true);
Console.WriteLine("Deleted random folder: " + fullPath); Console.WriteLine("刪除 random folder: " + fullPath);
} }
catch (Exception ex) 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() public static string GetServerAddress()
{ {
return String.Format("http://{0}:{1}/", _localIP, _port); return String.Format("http://{0}:{1}/", _localIP, _port);
// return String.Format("http://111.246.145.170:8080/");
} }
/// <summary> /// <summary>
/// 生成隨機路徑 /// 生成隨機路徑

View File

@ -4,13 +4,17 @@ namespace DualScreenDemo
{ {
public static class HttpServerManager public static class HttpServerManager
{ {
private static CancellationTokenSource _cts;
public static async void StartServer() public static async void StartServer()
{ {
int httpPort = 9090; // 你可以修改此端口 int httpPort = 9090; // 你可以修改此端口
string baseDirectory = Path.Combine(Application.StartupPath, @"themes\superstar\_www"); string baseDirectory = Path.Combine(Application.StartupPath, @"themes\superstar\_www");
CleanUpDirectory(baseDirectory); 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);
} }

View File

@ -62,11 +62,12 @@ namespace OverlayFormObj
get { return _mainForm; } get { return _mainForm; }
private set { _mainForm = value; } private set { _mainForm = value; }
} }
public static OverlayForm Instance { get; private set; }
public OverlayForm() public OverlayForm()
{ {
SetStyle(ControlStyles.SupportsTransparentBackColor, true); SetStyle(ControlStyles.SupportsTransparentBackColor, true);
MainForm = this; MainForm = this;
Instance = this;
InitializeFormSettings(); InitializeFormSettings();
ConfigureTimers(); ConfigureTimers();
InitializeLabels(); InitializeLabels();
@ -1512,7 +1513,6 @@ private void DisplayArtists(List<Artist> artists, int page)//歌星點進去後
ClearDisplay(); ClearDisplay();
DisplaySongs(currentPage); DisplaySongs(currentPage);
} }
/* 秒數偵測BUG 測試 */
public void AddSongToPlaylist(SongData songData) public void AddSongToPlaylist(SongData songData)
{ {
try try

View File

@ -84,7 +84,6 @@ namespace DualScreenDemo
myFavoritesButton.BackgroundImage = myFavoritesNormalBackground; myFavoritesButton.BackgroundImage = myFavoritesNormalBackground;
promotionsButton.BackgroundImage = promotionsNormalBackground; promotionsButton.BackgroundImage = promotionsNormalBackground;
deliciousFoodButton.BackgroundImage = deliciousFoodNormalBackground; deliciousFoodButton.BackgroundImage = deliciousFoodNormalBackground;
activeButton.BackgroundImage = activeBackground; activeButton.BackgroundImage = activeBackground;
} }
@ -144,13 +143,8 @@ namespace DualScreenDemo
private void HideQRCode() private void HideQRCode()
{ {
if (pictureBoxQRCode != null) pictureBoxQRCode.Visible = false;
{ closeQRCodeButton.Visible = false;
pictureBoxQRCode.Visible = false;
}
if(closeQRCodeButton != null){
closeQRCodeButton.Visible = false;
}
} }
private void InitializeButtonsForHotSong() private void InitializeButtonsForHotSong()

View File

@ -3,10 +3,10 @@ namespace DualScreenDemo
{ {
public partial class PrimaryForm : Form public partial class PrimaryForm : Form
{ {
private PictureBox pictureBoxQRCode; public PictureBox pictureBoxQRCode;
private Button closeQRCodeButton; public Button closeQRCodeButton;
private void OverlayQRCodeOnImage(string randomFolderPath) public void OverlayQRCodeOnImage(string randomFolderPath)
{ {
try try
{ {
@ -40,17 +40,19 @@ namespace DualScreenDemo
string qrImagePath = Path.Combine(Application.StartupPath, "themes/superstar/_www", randomFolderPath, "qrcode.png"); string qrImagePath = Path.Combine(Application.StartupPath, "themes/superstar/_www", randomFolderPath, "qrcode.png");
if (!File.Exists(qrImagePath)) if (!File.Exists(qrImagePath))
{ {
Console.WriteLine("QR code image not found: " + qrImagePath); Console.WriteLine("布局 image not found: " + qrImagePath);
return; return;
} }
string tempPath = Path.GetTempFileName();
File.Copy(qrImagePath, tempPath, true); // 複製一份再讀,避開快取
Image qrCodeImage = null; Image qrCodeImage = null;
for (int i = 0; i < 3; i++) for (int i = 0; i < 3; i++)
{ {
try 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); qrCodeImage = Image.FromStream(fs);
} }
@ -59,9 +61,10 @@ namespace DualScreenDemo
catch (Exception ex) catch (Exception ex)
{ {
Console.WriteLine("Error loading QR code image: " + ex.Message); Console.WriteLine("Error loading QR code image: " + ex.Message);
System.Threading.Thread.Sleep(100); Thread.Sleep(100);
} }
} }
File.Delete(tempPath); // 用完記得刪除
if (qrCodeImage == null) if (qrCodeImage == null)
{ {
@ -78,15 +81,10 @@ namespace DualScreenDemo
{ {
g.DrawImage(baseImage, 0, 0); g.DrawImage(baseImage, 0, 0);
Rectangle qrCodeRect = new Rectangle(32, 39, 165, 165); Rectangle qrCodeRect = new Rectangle(32, 39, 165, 165);
g.DrawImage(qrCodeImage, qrCodeRect); g.DrawImage(qrCodeImage, qrCodeRect);
} }
pictureBoxQRCode.Image = new Bitmap(bitmap); pictureBoxQRCode.Image = new Bitmap(bitmap);
} }
} }

View File

@ -339,10 +339,14 @@ namespace DualScreenDemo
buttonTopLeft.BringToFront(); buttonTopLeft.BringToFront();
buttonThanks.BringToFront(); buttonThanks.BringToFront();
} }
// 修正螢幕初始化關鍵
public void HideSendOffScreen() public void HideSendOffScreen()
{ {
sendOffPanel.Visible = false; sendOffPanel.Visible = false;
foreach (Control ctrl in this.Controls)
{
ctrl.Enabled = true;
}
} }
private void HideControlsRecursively(Control parent) private void HideControlsRecursively(Control parent)
@ -2458,11 +2462,47 @@ namespace DualScreenDemo
// 添加 Form Load 事件處理方法 // 添加 Form Load 事件處理方法
private void PrimaryForm_Load(object sender, EventArgs e) private void PrimaryForm_Load(object sender, EventArgs e)
{ {
// 確保所有控件都已初始化完成後,再觸發熱門排行按鈕點擊
if (hotPlayButton != null) if (hotPlayButton != null)
{ {
HotPlayButton_Click(null, EventArgs.Empty); 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) private void AutoRefreshTimer_Tick(object sender, EventArgs e)

View File

@ -87,9 +87,10 @@ namespace DualScreenDemo
//sender.Start(); //sender.Start();
// Console.WriteLine($"heart beat for server{sender.RemoteEndPoint.Address}:{sender.RemoteEndPoint.Port}"); // Console.WriteLine($"heart beat for server{sender.RemoteEndPoint.Address}:{sender.RemoteEndPoint.Port}");
try { try {
string stateFilePath = Path.Combine(AppDomain.CurrentDomain.BaseDirectory,"txt","states.txt"); string stateFilePath = Path.Combine(AppDomain.CurrentDomain.BaseDirectory,"txt","states.txt");
string initialState = ReadStateFile(stateFilePath); string initialState = ReadStateFile(stateFilePath);
/*
if (initialState.Equals("CLOSE", StringComparison.OrdinalIgnoreCase)) if (initialState.Equals("CLOSE", StringComparison.OrdinalIgnoreCase))
{ {
_ = SafeInvoke(PrimaryForm.Instance, () => _ = SafeInvoke(PrimaryForm.Instance, () =>
@ -106,6 +107,7 @@ namespace DualScreenDemo
} }
}); });
} }
*/
while (true) while (true)
{ {
@ -139,11 +141,13 @@ namespace DualScreenDemo
if (command.Trim().Equals("X", StringComparison.OrdinalIgnoreCase)) if (command.Trim().Equals("X", StringComparison.OrdinalIgnoreCase))
{ {
PrimaryForm.Instance.pictureBoxQRCode.Visible = false;
PrimaryForm.Instance.closeQRCodeButton.Visible = false;
_ = SafeInvoke(VideoPlayerForm.Instance, async () => _ = SafeInvoke(VideoPlayerForm.Instance, async () =>
{ {
if (IsFormReady(PrimaryForm.Instance)) if (IsFormReady(PrimaryForm.Instance))
{ {
await SafeInvoke(PrimaryForm.Instance, () => await SafeInvoke(PrimaryForm.Instance, async () =>
{ {
PrimaryForm.Instance.ShowSendOffScreen(); PrimaryForm.Instance.ShowSendOffScreen();
@ -178,7 +182,14 @@ namespace DualScreenDemo
VideoPlayerForm.Instance.PlayNextSong(); VideoPlayerForm.Instance.PlayNextSong();
PrimaryForm.Instance.logout(); PrimaryForm.Instance.logout();
Console.WriteLine("已設置新的播放列表,包含當前歌曲和 CLOSE.MPG"); Console.WriteLine("已設置新的播放列表,包含當前歌曲和 CLOSE.MPG");
try
{
await HttpServer.RestartServer();
}
catch (Exception ex)
{
Console.WriteLine("RestartServer 發生錯誤: " + ex.Message);
}
} }
else else
{ {