Compare commits

...

2 Commits

Author SHA1 Message Date
3f272cbdf4 少傳 2025-07-03 18:13:30 +08:00
8046eb5a35 新增 設定檔
調整 心跳功能
修正 藏鏡人
修正 服務鈴
20250703
2025-07-03 18:11:43 +08:00
27 changed files with 534 additions and 838 deletions

View File

@ -70,7 +70,6 @@ namespace DualScreenDemo
case "A26CA4":
Console.WriteLine("ToggleVocalRemoval Invoked");
SafeInvokeAction("A26CA4",() => VideoPlayerForm.Instance.ToggleVocalRemoval());
SafeInvokeAction("A26CA4",() => OverlayForm.MainForm.ShowOriginalSongLabel());
break;
// 導唱
case "A26EA4":
@ -180,6 +179,10 @@ namespace DualScreenDemo
SafeInvokeAction("A27BA4",() => OverlayForm.MainForm.HideAllLabels());
SafeInvokeAction("A27BA4",() => OverlayForm.MainForm.ShowKeyDownLabel("↓降4調"));
break;
case "A266A4":
SafeInvokeAction("A266A4",() => OverlayForm.MainForm.HideAllLabels());
SafeInvokeAction("A266A4",() => OverlayForm.MainForm.ShowServiceBell());
break;
default:
if (Regex.IsMatch(indata, @"^A23\d+A4$"))
{
@ -189,20 +192,8 @@ namespace DualScreenDemo
}
}
private Dictionary<string, DateTime> _recentCommands = new();
private readonly TimeSpan _debounceInterval = TimeSpan.FromMilliseconds(300); // 最短觸發間隔
private void SafeInvokeAction(string commandKey, Action action)
{
/*var now = DateTime.Now;
if (_recentCommands.TryGetValue(commandKey, out DateTime lastTime))
{
if (now - lastTime < _debounceInterval)
return; // 忽略短時間內的重複指令
}
_recentCommands[commandKey] = now;
*/
// 真正執行 UI 操作
if (OverlayForm.MainForm.InvokeRequired)
{

View File

@ -51,11 +51,11 @@ namespace DBObj
}
*/
public List<SongData> SearchNewSongs(){
string query= $"SELECT * FROM song_library_cache WHERE language_name = '國語' ORDER BY add_date DESC LIMIT {PrimaryForm.ReadNewSongLimit()};";
string query= $"SELECT * FROM song_library_cache WHERE language_name = '國語' ORDER BY add_date DESC LIMIT {Utils.Env.GetInt("NewSongLimit", 100)};";
return PrimaryForm.Instance.SearchSongs_Mysql(query);
}
public List<SongData> SearchHotSongs(){
string query= $"SELECT * FROM song_library_cache WHERE language_name = '國語' ORDER BY song_counts DESC LIMIT {PrimaryForm.ReadHotSongLimit()};";
string query= $"SELECT * FROM song_library_cache WHERE language_name = '國語' ORDER BY song_counts DESC LIMIT {Utils.Env.GetInt("HotSongLimit", 100)};";
return PrimaryForm.Instance.SearchSongs_Mysql(query);
}
public List<SongData> SearchSongsBySinger(string keyword)

View File

@ -1,140 +0,0 @@
using System.IO;
namespace DataCheck
{
public class dataCheck
{
public dataCheck()
{
menu_check();
news_check();
}
private void menu_check()
{
string menuPath = @"\\JLDKTV\foods";
string menuPath_local = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "foods");
if (!Directory.Exists(menuPath_local))
{
Directory.CreateDirectory(menuPath_local);
}
var serverFiles = Directory.GetFiles(menuPath, "*.jpg")
.Select(f => new FileInfo(f))
.ToDictionary(f => f.Name, f => f);
var localFiles = Directory.GetFiles(menuPath_local, "*.jpg")
.Select(f => new FileInfo(f))
.ToDictionary(f => f.Name, f => f);
foreach (var serverFile in serverFiles)
{
bool needsCopy = false;
string localFilePath = Path.Combine(menuPath_local, serverFile.Key);
if (!localFiles.ContainsKey(serverFile.Key))
{
needsCopy = true;
}
else
{
var localFile = localFiles[serverFile.Key];
if (serverFile.Value.LastWriteTime > localFile.LastWriteTime)
{
needsCopy = true;
}
}
if (needsCopy)
{
try
{
File.Copy(serverFile.Value.FullName, localFilePath, true);
Console.WriteLine($"更新菜單: {serverFile.Key}");
}
catch (Exception ex)
{
Console.WriteLine($"複製菜單失敗 {serverFile.Key}: {ex.Message}");
}
}
}
// 3-2. 清除本地有但伺服器已經沒有的檔案
foreach (var localFile in localFiles)
{
if (!serverFiles.ContainsKey(localFile.Key))
{
try
{
File.Delete(localFile.Value.FullName);
Console.WriteLine($"刪除本地多餘菜單: {localFile.Key}");
}
catch (Exception ex)
{
Console.WriteLine($"刪除菜單失敗 {localFile.Key}: {ex.Message}");
}
}
}
}
private void news_check()
{
string newsPath = @"\\JLDKTV\news";
string newsPath_local = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "news");
if (!Directory.Exists(newsPath_local))
{
Directory.CreateDirectory(newsPath_local);
}
var serverFiles = Directory.GetFiles(newsPath, "*.jpg")
.Select(f => new FileInfo(f))
.ToDictionary(f => f.Name, f => f);
var localFiles = Directory.GetFiles(newsPath_local, "*.jpg")
.Select(f => new FileInfo(f))
.ToDictionary(f => f.Name, f => f);
foreach (var serverFile in serverFiles)
{
bool needsCopy = false;
string localFilePath = Path.Combine(newsPath_local, serverFile.Key);
if (!localFiles.ContainsKey(serverFile.Key))
{
needsCopy = true;
}
else
{
var localFile = localFiles[serverFile.Key];
if (serverFile.Value.LastWriteTime > localFile.LastWriteTime)
{
needsCopy = true;
}
}
if (needsCopy)
{
try
{
File.Copy(serverFile.Value.FullName, localFilePath, true);
Console.WriteLine($"更新新聞: {serverFile.Key}");
}
catch (Exception ex)
{
Console.WriteLine($"複製新聞失敗 {serverFile.Key}: {ex.Message}");
}
}
}
// 3-2. 清除本地有但伺服器已經沒有的檔案
foreach (var localFile in localFiles)
{
if (!serverFiles.ContainsKey(localFile.Key))
{
try
{
File.Delete(localFile.Value.FullName);
Console.WriteLine($"刪除本地多餘新聞: {localFile.Key}");
}
catch (Exception ex)
{
Console.WriteLine($"刪除新聞失敗 {localFile.Key}: {ex.Message}");
}
}
}
}
}
}

61
Env.cs Normal file
View File

@ -0,0 +1,61 @@
using System;
using System.Collections.Generic;
using System.IO;
namespace Utils
{
public static class Env
{
public static readonly string KtvPath = @"\\pc101\KTVData";
private static readonly Dictionary<string, string> _values;
static Env()
{
var path = Path.Combine(KtvPath, "config.ini");
_values = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase);
if (!File.Exists(path))
{
Console.WriteLine($"❌ 找不到環境檔案:{path}");
return;
}
foreach (var line in File.ReadAllLines(path))
{
var trimmed = line.Trim();
if (string.IsNullOrWhiteSpace(trimmed) || trimmed.StartsWith("#")) continue;
var index = trimmed.IndexOf('=');
if (index < 0) continue;
var key = trimmed[..index].Trim();
var value = trimmed[(index + 1)..].Trim();
if (value.StartsWith("\"") && value.EndsWith("\""))
value = value[1..^1]; // 去除引號
_values[key] = value;
}
}
public static string Get(string key, string fallback = "")
{
return _values.TryGetValue(key, out var value) ? value : fallback;
}
public static bool GetBool(string key, bool fallback = false)
{
return _values.TryGetValue(key, out var value) && bool.TryParse(value, out var result)
? result
: fallback;
}
public static int GetInt(string key, int fallback = 0)
{
return _values.TryGetValue(key, out var value) && int.TryParse(value, out var result)
? result
: fallback;
}
// 可加上 GetFloat、GetDouble、GetTimeSpan ... 視需要
}
}

View File

@ -1,182 +1,156 @@
using System.Net.Http;
using System.Text;
using System.Text.Json; // 適用於 .NET Core 3.0+ / .NET 5/6/7/8
using System.Text.Json;
using System.Net;
using System.Net.Sockets;
using System.Diagnostics;
using System.IO;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.VisualBasic.Devices;
namespace HeartbeatSender
namespace HeartbeatSender;
public class heartbeatSender
{
private readonly HttpClient httpClient = new();
private string token = "";
private string branchName = "";
private string heartbeatDomain = "";
private CancellationTokenSource? cancellationTokenSource;
public class heartbeatSender
public heartbeatSender()
{
private readonly HttpClient httpClient = new HttpClient();
private string token;
private string token_heartbeatUrl;
private string init_heartbeatUrl;
this.heartbeatDomain = Utils.Env.Get("HeartBeatUrl", "");
public heartbeatSender()
// 在建構子中啟動背景心跳任務
_ = Task.Run(() => StartAsync());
}
public async Task StartAsync()
{
cancellationTokenSource = new CancellationTokenSource();
while (!cancellationTokenSource.IsCancellationRequested)
{
LoadHeartbeatUrls("txt/HeartBeat.txt");
}
/// <summary>
/// 讀取URL
/// </summary>
/// <param name="filePath">路徑</param>
private void LoadHeartbeatUrls(string filePath)
{
if (!File.Exists(filePath))
{
Console.WriteLine("找不到 HeartBeat.txt");
return;
}
string[] lines = File.ReadAllLines(filePath);
foreach (string line in lines)
{
if (line.StartsWith("init:"))
{
init_heartbeatUrl = line.Substring("init:".Length).Trim();
}
else if (line.StartsWith("token:"))
{
token_heartbeatUrl = line.Substring("token:".Length).Trim();
}
}
Console.WriteLine("init URL: " + init_heartbeatUrl);
Console.WriteLine("token URL: " + token_heartbeatUrl);
}
public static string GetLocalIPv4()
{
foreach (var ip in Dns.GetHostEntry(Dns.GetHostName()).AddressList)
{
if (ip.AddressFamily == AddressFamily.InterNetwork && !IPAddress.IsLoopback(ip))
{
return ip.ToString();
}
}
return "127.0.0.1"; // fallback
}
public async Task<bool> LoginAndGetTokenAsync()
{
// init_heartbeat
//var loginUrl = "http://zqd.superstar.dnsnet.cc/api/room/receiveRegister";
string hostName = System.Net.Dns.GetHostName();
var loginPayload = new
{
branch_name = "測試",
room_name = "PC" + hostName.Substring(Math.Max(0, hostName.Length - 3)),
room_ip = GetLocalIPv4(),
email = "MachineKTV@gmail.com",
password = "aa147258-"
};
var json = JsonSerializer.Serialize(loginPayload);
var content = new StringContent(json, Encoding.UTF8, "application/json");
try
{
var response = await httpClient.PostAsync(init_heartbeatUrl, content);
response.EnsureSuccessStatusCode();
var responseJson = await response.Content.ReadAsStringAsync();
// Console.WriteLine("API 回傳內容:" + responseJson);
using var doc = JsonDocument.Parse(responseJson);
if (doc.RootElement.TryGetProperty("data", out JsonElement dataElement) &&
dataElement.ValueKind == JsonValueKind.Object)
if (string.IsNullOrEmpty(token))
{
if (dataElement.TryGetProperty("token", out JsonElement tokenElement))
{
token = tokenElement.GetString();
}
bool loginSuccess = await LoginAndGetTokenAsync();
Console.WriteLine(loginSuccess ? "心跳登入成功" : "心跳登入失敗");
}
// Console.WriteLine("登入成功,取得 token" + token);
return true;
if (!string.IsNullOrEmpty(token))
{
await SendHeartbeatAsync();
}
await Task.Delay(TimeSpan.FromMinutes(1), cancellationTokenSource.Token);
}
catch (Exception ex)
{
Console.WriteLine($"登入失敗:{ex.Message}");
return false;
Console.WriteLine($"心跳任務錯誤:{ex.Message}");
await Task.Delay(5000); // 錯誤後延遲再跑
}
}
public async Task SendHeartbeatAsync()
{
if (string.IsNullOrEmpty(token))
{
Console.WriteLine("請先登入取得 token");
return;
}
//Console.WriteLine(GetLocalIPv4());
string hostName = System.Net.Dns.GetHostName();
var heartbeatData = new
{
branch_name = "測試",
hostname = "PC" + hostName.Substring(Math.Max(0, hostName.Length - 3)),
ip = GetLocalIPv4(),
cpu = GetCpuUsage(),
memory = GetTotalMemoryInMB(),
disk = GetDiskTotalSizeInGB()
};
var json = JsonSerializer.Serialize(heartbeatData);
var content = new StringContent(json, Encoding.UTF8, "application/json");
// token_heartbeat
// heartbeatUrl = "http://zqd.superstar.dnsnet.cc/api/room/heartbeat";
var request = new HttpRequestMessage(HttpMethod.Post, token_heartbeatUrl);
request.Content = content;
request.Headers.Authorization = new System.Net.Http.Headers.AuthenticationHeaderValue("Bearer", token);
// Console.WriteLine("送出的 JSON");
// Console.WriteLine(json);
var response = await httpClient.SendAsync(request);
try
{
// Console.WriteLine($"心跳送出狀態:{response.StatusCode}");
response.EnsureSuccessStatusCode();
var responseJson = await response.Content.ReadAsStringAsync();
using var doc = JsonDocument.Parse(responseJson);
// Console.WriteLine("API 回傳內容:" + responseJson);
}
catch (Exception ex)
{
var errorContent = await response.Content.ReadAsStringAsync();
Console.WriteLine($"送出心跳錯誤:{ex.Message}");
Console.WriteLine($"後端回應內容:{errorContent}");
}
}
private float GetCpuUsage()
{
using var cpuCounter = new PerformanceCounter("Processor", "% Processor Time", "_Total");
cpuCounter.NextValue(); // 需要呼叫兩次
System.Threading.Thread.Sleep(100);
return cpuCounter.NextValue(); // 回傳的是百分比,例如 25.3
}
private float GetTotalMemoryInMB()
{
var computerInfo = new ComputerInfo();
var totalMB = computerInfo.TotalPhysicalMemory / (1024f);
var availableMB = computerInfo.AvailablePhysicalMemory / (1024f);
var usedMB = totalMB - availableMB;
return usedMB; // 轉 MB
}
private float GetDiskTotalSizeInGB(string driveLetter = "C")
{
var drive = new DriveInfo(driveLetter);
return drive.TotalSize / (1024f * 1024f * 1024f); // 轉 GB
}
}
public void Stop()
{
cancellationTokenSource?.Cancel();
Console.WriteLine("心跳任務已停止");
}
public async Task<bool> LoginAndGetTokenAsync()
{
var url = $"{heartbeatDomain.TrimEnd('/')}/api/room/receiveRegister";
var loginPayload = new
{
email = "MachineKTV@gmail.com",
password = "aa147258-"
};
var result = await SendPostAsync<JsonElement>(url, loginPayload);
if (result.ValueKind == JsonValueKind.Object &&
result.TryGetProperty("data", out JsonElement data))
{
token = data.GetProperty("token").GetString()!;
branchName = data.GetProperty("branch_name").GetString()!;
return true;
}
return false;
}
public async Task SendHeartbeatAsync()
{
var url = $"{heartbeatDomain.TrimEnd('/')}/api/room/heartbeat";
string hostName = Dns.GetHostName();
var heartbeatPayload = new
{
branch_name = branchName,
hostname = "PC" + hostName[^3..],
ip = GetLocalIPv4(),
cpu = GetCpuUsage(),
memory = GetTotalMemoryInMB(),
disk = GetDiskTotalSizeInGB()
};
await SendPostAsync<JsonElement>(url, heartbeatPayload, token);
Console.WriteLine($"心跳送出成功: {DateTime.Now:HH:mm:ss}");
}
private async Task<T?> SendPostAsync<T>(string url, object payload, string? bearerToken = null)
{
var json = JsonSerializer.Serialize(payload);
var content = new StringContent(json, Encoding.UTF8, "application/json");
var request = new HttpRequestMessage(HttpMethod.Post, url)
{
Content = content
};
if (!string.IsNullOrWhiteSpace(bearerToken))
{
request.Headers.Authorization = new System.Net.Http.Headers.AuthenticationHeaderValue("Bearer", bearerToken);
}
var response = await httpClient.SendAsync(request);
response.EnsureSuccessStatusCode();
string responseJson = await response.Content.ReadAsStringAsync();
return JsonSerializer.Deserialize<T>(responseJson);
}
private static string GetLocalIPv4()
{
foreach (var ip in Dns.GetHostEntry(Dns.GetHostName()).AddressList)
{
if (ip.AddressFamily == AddressFamily.InterNetwork && !IPAddress.IsLoopback(ip))
return ip.ToString();
}
return "127.0.0.1";
}
private float GetCpuUsage()
{
using var cpuCounter = new PerformanceCounter("Processor", "% Processor Time", "_Total");
cpuCounter.NextValue();
Thread.Sleep(100);
return cpuCounter.NextValue();
}
private float GetTotalMemoryInMB()
{
var ci = new ComputerInfo();
return (ci.TotalPhysicalMemory - ci.AvailablePhysicalMemory) / 1024f;
}
private float GetDiskTotalSizeInGB(string driveLetter = "C")
{
var drive = new DriveInfo(driveLetter);
return drive.TotalSize / (1024f * 1024f * 1024f);
}
}

View File

@ -42,9 +42,7 @@ namespace DualScreenDemo
// 安裝包更新
string localAddress = GetLocalIPAddress();
string externalAddress = File.Exists(@"\\JLDKTV\txt\ip.txt")
? File.ReadAllText(@"\\JLDKTV\txt\ip.txt").Trim()
: "";
string externalAddress = Utils.Env.Get("PhoneIP", "").Trim();
_listener = new HttpListener();
_listener.Prefixes.Add($"http://{localAddress}:{port}/");
@ -538,7 +536,7 @@ namespace DualScreenDemo
// 执行服务操作
PrimaryForm.SendCommandThroughSerialPort("a2 53 a4");
OverlayForm.MainForm.Invoke(new System.Action(() => {
OverlayForm.MainForm.ShowServiceBellLabel();
OverlayForm.MainForm.ShowServiceBell();
}));
// 异步处理等待和隐藏标签
await HttpServer.HandleServiceBellAsync();
@ -983,9 +981,19 @@ namespace DualScreenDemo
string Messagelast="";
for(int j=0;j < message.Length;j++){
Messagelast += message[j];
await Task.Delay(300);
await Task.Delay(10);
// 将读取到的 "message" 字段传递给 UI 控件显示
InvokeAction(() => OverlayForm.MainForm.ShowmessageLabel(Messagefist+Messagelast+'_'));
if (OverlayForm.MainForm.InvokeRequired)
{
OverlayForm.MainForm.Invoke(new System.Action(() =>
{
OverlayForm.MainForm.ShowmessageLabel(Messagefist + Messagelast + '_');
}));
}
else
{
OverlayForm.MainForm.ShowmessageLabel(Messagefist + Messagelast + '_');
}
}
// 真情告白顯示秒數
await Task.Delay(3000);

View File

@ -14,8 +14,12 @@ namespace OverlayFormObj
public Label originalSongLabel;
public Label nextSongLabel;
private Label serviceBellLabel;
private Label BellLabel;
private Label OriginalLabel;
public Label volumeUpLabel; // New volume up label
public Label volumeDownLabel; // New volume down label
private System.Windows.Forms.Timer BellTimer = new System.Windows.Forms.Timer();
private System.Windows.Forms.Timer OriginalTimer = new System.Windows.Forms.Timer();
private System.Windows.Forms.Timer volumeUpTimer = new System.Windows.Forms.Timer();
private System.Windows.Forms.Timer volumeDownTimer = new System.Windows.Forms.Timer();
private System.Windows.Forms.Timer micUpTimer = new System.Windows.Forms.Timer();
@ -60,6 +64,10 @@ namespace OverlayFormObj
InitializeMuteLabel(); // Initialize the new mute label
InitializeOriginalSongLabel(); // 初始化原唱标签
InitializeServiceBellLabel();
InitializeBellLabel();
InitializeOriginalLabel();
InitializeVolumeUpLabel(); // Initialize the volume up label
InitializeVolumeDownLabel(); // Initialize the volume down label
InitializeMicUpLabel(); // Initialize the microphone up label
@ -89,6 +97,12 @@ namespace OverlayFormObj
private void ConfigureKeyTimers()
{
OriginalTimer.Interval = 1000;
OriginalTimer.Tick += (sender, e) => {OriginalLabel.Visible = false;OriginalTimer.Stop();keepshowmic();};
BellTimer.Interval = 1000;
BellTimer.Tick += (sender, e) => {BellLabel.Visible = false;BellTimer.Stop();keepshowmic();};
volumeUpTimer.Interval = 1000;
volumeUpTimer.Tick += (sender, e) => {volumeUpLabel.Visible = false;volumeUpTimer.Stop();keepshowmic();};
@ -422,13 +436,47 @@ namespace OverlayFormObj
volumeUpLabel.Location = new Point(blackBackgroundPanel.Width - volumeUpLabel.Width - 10, 56);
};
}
private void InitializeOriginalLabel()
{
OriginalLabel = new Label
{
AutoSize = true,
ForeColor = Color.White,
Font = new Font("Microsoft JhengHei", 34, FontStyle.Bold),
BackColor = Color.Transparent,
Text = "有人聲",
Visible = false
};
blackBackgroundPanel.Controls.Add(OriginalLabel);
blackBackgroundPanel.Resize += (s, e) =>
{
OriginalLabel.Location = new Point(blackBackgroundPanel.Width - OriginalLabel.Width - 10, 56);
};
}
private void InitializeBellLabel()
{
BellLabel = new Label
{
AutoSize = true,
ForeColor = Color.White,
Font = new Font("Microsoft JhengHei", 34, FontStyle.Bold),
BackColor = Color.Transparent,
Text = "服務鈴",
Visible = false
};
blackBackgroundPanel.Controls.Add(BellLabel);
blackBackgroundPanel.Resize += (s, e) =>
{
BellLabel.Location = new Point(blackBackgroundPanel.Width - BellLabel.Width - 10, 56);
};
}
private void InitializeVolumeDownLabel()
{
volumeDownLabel = new Label
{
AutoSize = true,
ForeColor = Color.White,
Font = new Font("Microsoft JhengHei",34, FontStyle.Bold),
Font = new Font("Microsoft JhengHei", 34, FontStyle.Bold),
BackColor = Color.Transparent,
Text = "音量 ↓",
Visible = false
@ -808,23 +856,18 @@ public void UpdateNextSongLabelFromPlaylist(bool isUserPlaylistPlaying, SongData
// 显示服务铃标签
public void ShowServiceBellLabel()
{
if (serviceBellLabel != null)
{
whichoneshowmic();
HideAllLabels();
serviceBellLabel.Visible = true;
// Console.WriteLine("服務鈴顯示: " + serviceBellLabel.Text); // 输出标签文本内容
}
Console.WriteLine("服務鈴顯示: " + serviceBellLabel.Text); // 输出标签文本内容
}
public void HideServiceBellLabel()
{
if (serviceBellLabel != null)
{
{ /*
serviceBellLabel.Visible = false;
// Console.WriteLine("服務鈴隱藏: " + serviceBellLabel.Text); // 输出标签文本内容
Console.WriteLine("服務鈴隱藏: " + serviceBellLabel.Text); // 输出标签文本内容
keepshowmic();
}
*/
}
// 显示标准标签
@ -1028,6 +1071,19 @@ public void UpdateNextSongLabelFromPlaylist(bool isUserPlaylistPlaying, SongData
volumeUpLabel.Visible = true;
volumeUpTimer.Start();
}
public void ShowServiceBell()
{
BellLabel.Visible = true;
BellTimer.Start();
}
public void ShowOriginalLabel(string text = "有人聲")
{
OriginalLabel.Text = text;
OriginalLabel.Visible = true;
OriginalTimer.Start();
}
// 显示音量-标签
public void ShowVolumeDownLabel()
{

View File

@ -444,35 +444,67 @@ private static void DisplayTimer_Tick(object sender, EventArgs e)
// 當 songDisplayTimer 計時完成時會呼叫此函式
private static void SongDisplayTimer_Elapsed(object sender, EventArgs e)
{
if (MainForm == null || MainForm.IsDisposed || MainForm.Disposing)
{
Console.WriteLine("MainForm is not valid.");
return;
}
// 檢查是否需要跨執行緒操作 UI 控制項
if (MainForm.InvokeRequired)
{
// 如果目前不在 UI 執行緒上,必須透過 Invoke 安全執行 UI 更新邏輯
Console.WriteLine("SongDisplayTimer_Tick invoked on UI thread.");
MainForm.BeginInvoke(new Action(() =>
try
{
if (MainForm.songDisplayLabel != null)
MainForm.songDisplayLabel.Text = "";
MainForm.BeginInvoke(new Action(() =>
{
try
{
if (MainForm.songDisplayLabel != null && !MainForm.songDisplayLabel.IsDisposed)
MainForm.songDisplayLabel.Text = "";
if (MainForm.nextSongLabel != null)
MainForm.nextSongLabel.Visible = true;
}));
if (MainForm.nextSongLabel != null && !MainForm.nextSongLabel.IsDisposed)
MainForm.nextSongLabel.Visible = true;
}
catch (Exception ex)
{
Console.WriteLine("Invoke UI update failed: " + ex.Message);
}
}));
}
catch (ObjectDisposedException ex)
{
Console.WriteLine("BeginInvoke failed: MainForm already disposed. " + ex.Message);
}
}
else
{
// 如果已經在 UI 執行緒,就直接更新 UI 控制項
Console.WriteLine("SongDisplayTimer_Tick invoked on background thread.");
MainForm.songDisplayLabel.Text = "";
MainForm.nextSongLabel.Visible = true;
try
{
if (MainForm.songDisplayLabel != null && !MainForm.songDisplayLabel.IsDisposed)
MainForm.songDisplayLabel.Text = "";
if (MainForm.nextSongLabel != null && !MainForm.nextSongLabel.IsDisposed)
MainForm.nextSongLabel.Visible = true;
}
catch (Exception ex)
{
Console.WriteLine("Direct UI update failed: " + ex.Message);
}
}
// 停止計時器,避免重複觸發
songDisplayTimer.Stop();
try
{
songDisplayTimer.Stop();
}
catch (Exception ex)
{
Console.WriteLine("Failed to stop timer: " + ex.Message);
}
}
private readonly object _lockObject = new object();
private bool _handlingTimeout = false;
private async void UnifiedTimer_Elapsed(object sender, EventArgs e)
@ -1495,11 +1527,11 @@ private void DisplayArtists(List<Artist> artists, int page)//歌星點進去後
if (category == Category.NewSongs)
{
sqlQuery = $"SELECT * FROM song_library_cache WHERE language_name = '{language}' ORDER BY add_date DESC LIMIT {PrimaryForm.ReadNewSongLimit()};";
sqlQuery = $"SELECT * FROM song_library_cache WHERE language_name = '{language}' ORDER BY add_date DESC LIMIT {Utils.Env.GetInt("NewSongLimit", 100)};";
}
else if (category == Category.HotSongs)
{
sqlQuery = $"SELECT * FROM song_library_cache WHERE language_name = '{language}' ORDER BY song_counts DESC LIMIT {PrimaryForm.ReadHotSongLimit()};";
sqlQuery = $"SELECT * FROM song_library_cache WHERE language_name = '{language}' ORDER BY song_counts DESC LIMIT {Utils.Env.GetInt("HotSongLimit", 100)};";
}
else
{

View File

@ -5,8 +5,8 @@ namespace DualScreenDemo
public partial class PrimaryForm
{
private Button hotPlayButton;
private Bitmap hotPlayNormalBackground;
private Bitmap hotPlayActiveBackground;
private Bitmap hotPlayNormalBackground;
private Bitmap hotPlayActiveBackground;
private Button guoYuButtonHotSong;
private Bitmap guoYuHotSongNormalBackground;
@ -36,18 +36,18 @@ namespace DualScreenDemo
private void SetHotSongButtonsVisibility(bool isVisible)
{
Button[] hotSongButtons = { guoYuButtonHotSong, taiYuButtonHotSong, taiYuNewSongButtonHotSong, guoYuNewSongButtonHotSong, yingWenButtonHotSong, riYuButtonHotSong, hanYuButtonHotSong };
foreach (var button in hotSongButtons)
{
button.Visible = isVisible;
if (isVisible)
{
button.BringToFront();
@ -62,11 +62,10 @@ namespace DualScreenDemo
isOnOrderedSongsPage = false;
/* 清空搜尋欄 */
ResetinputBox();
int songLimit = ReadHotSongLimit();
string query = $"SELECT * FROM song_library_cache WHERE language_name = '國語' ORDER BY `song_counts` DESC LIMIT {songLimit};";
string query = $"SELECT * FROM song_library_cache WHERE language_name = '國語' ORDER BY `song_counts` DESC LIMIT {Utils.Env.GetInt("HotSongLimit", 100)};";
var guoYuSongs = SearchSongs_Mysql(query);
UpdateSongList(guoYuSongs);
SetButtonsVisibility();
//HideQRCode();
}
@ -91,8 +90,7 @@ namespace DualScreenDemo
{
UpdateHotSongButtons(activeButton, activeBackground);
int songLimit = ReadHotSongLimit();
string query = $"SELECT * FROM song_library_cache WHERE language_name = '{category}' ORDER BY `song_counts` DESC LIMIT {songLimit};";
string query = $"SELECT * FROM song_library_cache WHERE language_name = '{category}' ORDER BY `song_counts` DESC LIMIT {Utils.Env.GetInt("HotSongLimit", 100)};";
var selectedSongs = SearchSongs_Mysql(query);
UpdateSongList(selectedSongs);
@ -113,11 +111,11 @@ namespace DualScreenDemo
private void UpdateSongList(List<SongData> songs)
{
currentPage = 0;
currentSongList = songs;
currentPage = 0;
currentSongList = songs;
totalPages = (int)Math.Ceiling((double)songs.Count / itemsPerPage);
multiPagePanel.currentPageIndex = 0;
multiPagePanel.LoadSongs(currentSongList);
}
@ -148,85 +146,86 @@ namespace DualScreenDemo
}
private void InitializeButtonsForHotSong()
{
{
InitializeHotSongButton(ref guoYuNewSongButtonHotSong, "國語新歌", 1214, 230, 209, 58,
normalStateImageHotSong,
out guoYuNewSongHotSongNormalBackground,
mouseDownImageHotSong,
out guoYuNewSongHotSongActiveBackground,
InitializeHotSongButton(ref guoYuNewSongButtonHotSong, "國語新歌", 1214, 230, 209, 58,
normalStateImageHotSong,
out guoYuNewSongHotSongNormalBackground,
mouseDownImageHotSong,
out guoYuNewSongHotSongActiveBackground,
GuoYuNewSongButtonHotSong_Click);
InitializeHotSongButton(ref taiYuNewSongButtonHotSong, "台語新歌", 1214, 293, 209, 58,
normalStateImageHotSong,
out taiYuNewSongHotSongNormalBackground,
mouseDownImageHotSong,
out taiYuNewSongHotSongActiveBackground,
InitializeHotSongButton(ref taiYuNewSongButtonHotSong, "台語新歌", 1214, 293, 209, 58,
normalStateImageHotSong,
out taiYuNewSongHotSongNormalBackground,
mouseDownImageHotSong,
out taiYuNewSongHotSongActiveBackground,
TaiYuNewSongButtonHotSong_Click);
InitializeHotSongButton(ref taiYuButtonHotSong, "台語", 1214, 418, 209, 58,
normalStateImageHotSong,
out taiYuHotSongNormalBackground,
mouseDownImageHotSong,
out taiYuHotSongActiveBackground,
InitializeHotSongButton(ref taiYuButtonHotSong, "台語", 1214, 418, 209, 58,
normalStateImageHotSong,
out taiYuHotSongNormalBackground,
mouseDownImageHotSong,
out taiYuHotSongActiveBackground,
TaiYuButtonHotSong_Click);
InitializeHotSongButton(ref yueYuButtonHotSong, "粵語", 1214, 356, 209, 58,
normalStateImageHotSong,
out yueYuHotSongNormalBackground,
mouseDownImageHotSong,
out yueYuHotSongActiveBackground,
InitializeHotSongButton(ref yueYuButtonHotSong, "粵語", 1214, 356, 209, 58,
normalStateImageHotSong,
out yueYuHotSongNormalBackground,
mouseDownImageHotSong,
out yueYuHotSongActiveBackground,
YueYuButtonHotSong_Click);
InitializeHotSongButton(ref guoYuButtonHotSong, "國語", 1214, 356, 209, 59,
normalStateImageHotSong,
out guoYuHotSongNormalBackground,
mouseDownImageHotSong,
out guoYuHotSongActiveBackground,
InitializeHotSongButton(ref guoYuButtonHotSong, "國語", 1214, 356, 209, 59,
normalStateImageHotSong,
out guoYuHotSongNormalBackground,
mouseDownImageHotSong,
out guoYuHotSongActiveBackground,
GuoYuButtonHotSong_Click);
InitializeHotSongButton(ref yingWenButtonHotSong, "英文", 1214, 481, 209, 59,
normalStateImageHotSong,
out yingWenHotSongNormalBackground,
mouseDownImageHotSong,
out yingWenHotSongActiveBackground,
InitializeHotSongButton(ref yingWenButtonHotSong, "英文", 1214, 481, 209, 59,
normalStateImageHotSong,
out yingWenHotSongNormalBackground,
mouseDownImageHotSong,
out yingWenHotSongActiveBackground,
YingWenButtonHotSong_Click);
InitializeHotSongButton(ref riYuButtonHotSong, "日語", 1214, 544, 209, 59,
normalStateImageHotSong,
out riYuHotSongNormalBackground,
mouseDownImageHotSong,
out riYuHotSongActiveBackground,
InitializeHotSongButton(ref riYuButtonHotSong, "日語", 1214, 544, 209, 59,
normalStateImageHotSong,
out riYuHotSongNormalBackground,
mouseDownImageHotSong,
out riYuHotSongActiveBackground,
RiYuButtonHotSong_Click);
InitializeHotSongButton(ref hanYuButtonHotSong, "韓語", 1214, 607, 209, 58,
normalStateImageHotSong,
out hanYuHotSongNormalBackground,
mouseDownImageHotSong,
out hanYuHotSongActiveBackground,
InitializeHotSongButton(ref hanYuButtonHotSong, "韓語", 1214, 607, 209, 58,
normalStateImageHotSong,
out hanYuHotSongNormalBackground,
mouseDownImageHotSong,
out hanYuHotSongActiveBackground,
HanYuButtonHotSong_Click);
}
private void InitializeHotSongButton(ref Button button, string buttonText, int x, int y, int width, int height,
Image normalBackground, out Bitmap normalBackgroundOut,
Image activeBackground, out Bitmap activeBackgroundOut,
private void InitializeHotSongButton(ref Button button, string buttonText, int x, int y, int width, int height,
Image normalBackground, out Bitmap normalBackgroundOut,
Image activeBackground, out Bitmap activeBackgroundOut,
EventHandler clickEventHandler)
{
button = new Button {
button = new Button
{
Text = "", // 移除文字
Visible = false
Visible = false
};
ResizeAndPositionButton(button, x, y, width, height);
// 修改裁剪區域,避開文字部分
Rectangle cropArea = new Rectangle(1214, y, 209, 58); // 使用固定的裁剪區域
normalBackgroundOut = new Bitmap(normalBackground).Clone(cropArea, normalBackground.PixelFormat);
activeBackgroundOut = new Bitmap(activeBackground).Clone(cropArea, activeBackground.PixelFormat);
button.BackgroundImage = normalBackgroundOut;
button.BackgroundImageLayout = ImageLayout.Stretch;
button.FlatStyle = FlatStyle.Flat;
@ -234,36 +233,5 @@ namespace DualScreenDemo
button.Click += clickEventHandler;
this.Controls.Add(button);
}
public static int ReadHotSongLimit()
{
string filePath = Path.Combine(Application.StartupPath,"txt","SongLimitsSettings.txt");
try
{
var lines = File.ReadAllLines(filePath);
foreach (var line in lines)
{
if (line.StartsWith("HotSongLimit:"))
{
string valuePart = line.Split(':')[1].Trim();
int limit;
if (int.TryParse(valuePart, out limit))
{
return limit;
}
break;
}
}
}
catch (Exception ex)
{
Console.WriteLine("Failed to read song limits from file: " + ex.Message);
return 100;
}
return 100;
}
}
}

View File

@ -7,8 +7,7 @@ namespace DualScreenDemo
// 重置其他按钮背景
UpdateHotSongButtons(guoYuNewSongButtonHotSong, guoYuNewSongHotSongActiveBackground);
int songLimit = ReadHotSongLimit();
string query = $"SELECT * FROM song_library_cache WHERE language_name = '國語' ORDER BY `add_date` DESC, `song_counts` DESC LIMIT {songLimit};";
string query = $"SELECT * FROM song_library_cache WHERE language_name = '國語' ORDER BY `add_date` DESC, `song_counts` DESC LIMIT {Utils.Env.GetInt("HotSongLimit", 100)};";
var selectedSongs = PrimaryForm.Instance.SearchSongs_Mysql(query);
currentPage = 0;

View File

@ -7,9 +7,7 @@ namespace DualScreenDemo
// 重置其他按钮背景
UpdateHotSongButtons(taiYuNewSongButtonHotSong, taiYuNewSongHotSongActiveBackground);
int songLimit = ReadHotSongLimit();
string query = $"SELECT * FROM song_library_cache WHERE language_name = '台語' ORDER BY `add_date` DESC, `song_counts` DESC LIMIT {songLimit};";
string query = $"SELECT * FROM song_library_cache WHERE language_name = '台語' ORDER BY `add_date` DESC, `song_counts` DESC LIMIT {Utils.Env.GetInt("HotSongLimit", 100)};";
var selectedSongs = SearchSongs_Mysql(query);
currentPage = 0;

View File

@ -221,40 +221,8 @@ namespace DualScreenDemo
this.Controls.Add(hanYuButtonNewSong);
}
public static int ReadNewSongLimit()
{
string filePath = Path.Combine(Application.StartupPath, "txt", "SongLimitsSettings.txt");
try
{
var lines = File.ReadAllLines(filePath);
foreach (var line in lines)
{
if (line.StartsWith("NewSongLimit:"))
{
string valuePart = line.Split(':')[1].Trim();
int limit;
if (int.TryParse(valuePart, out limit))
{
return limit;
}
break;
}
}
}
catch (Exception ex)
{
Console.WriteLine("Failed to read song limits from file: " + ex.Message);
return 100;
}
return 100;
}
private string setQueryforNewSong(string category){
string query = $"SELECT * FROM song_library_cache WHERE language_name = '{category}' ORDER BY add_date DESC LIMIT {ReadNewSongLimit()};";
return query;
return $"SELECT * FROM song_library_cache WHERE language_name = '{category}' ORDER BY add_date DESC LIMIT {Utils.Env.GetInt("NewSongLimit", 100)};";
}
}
}

View File

@ -11,21 +11,13 @@ namespace DualScreenDemo
riYuButtonNewSong.BackgroundImage = riYuNewSongNormalBackground;
hanYuButtonNewSong.BackgroundImage = hanYuNewSongNormalBackground;
int songLimit = ReadNewSongLimit();
/*yueYuSongs2 = allSongs.Where(song => song.Category == "粵語")
.OrderByDescending(song => song.AddedTime)
.Take(songLimit)
.ToList();*/
string query = setQueryforNewSong("粵語");
var yueYuSongs2 = SearchSongs_Mysql(query);
var yueYuSongs2 = SearchSongs_Mysql(setQueryforNewSong("粵語"));
currentPage = 0;
currentSongList = yueYuSongs2;
totalPages = (int)Math.Ceiling((double)yueYuSongs2.Count / itemsPerPage);
multiPagePanel.currentPageIndex = 0;
multiPagePanel.LoadSongs(currentSongList);
multiPagePanel.LoadSongs(yueYuSongs2);
}
}
}

View File

@ -11,21 +11,13 @@ namespace DualScreenDemo
riYuButtonNewSong.BackgroundImage = riYuNewSongNormalBackground;
hanYuButtonNewSong.BackgroundImage = hanYuNewSongNormalBackground;
int songLimit = ReadNewSongLimit();
/*guoYuSongs2 = allSongs.Where(song => song.Category == "國語")
.OrderByDescending(song => song.AddedTime)
.Take(songLimit)
.ToList();*/
string query = setQueryforNewSong("國語");
var guoYuSongs2 = SearchSongs_Mysql(query);
var guoYuSongs2 = SearchSongs_Mysql(setQueryforNewSong("國語"));
currentPage = 0;
currentSongList = guoYuSongs2;
totalPages = (int)Math.Ceiling((double)guoYuSongs2.Count / itemsPerPage);
multiPagePanel.currentPageIndex = 0;
multiPagePanel.LoadSongs(currentSongList);
multiPagePanel.LoadSongs(guoYuSongs2);
}
}
}

View File

@ -11,22 +11,14 @@ namespace DualScreenDemo
riYuButtonNewSong.BackgroundImage = riYuNewSongNormalBackground;
hanYuButtonNewSong.BackgroundImage = hanYuNewSongNormalBackground;
int songLimit = ReadNewSongLimit();
/*yingWenSongs2 = allSongs.Where(song => song.Category == "英語")
.OrderByDescending(song => song.AddedTime)
.Take(songLimit)
.ToList();*/
string query = setQueryforNewSong("英語");
var yingWenSongs2 = SearchSongs_Mysql(query);
var yingWenSongs2 = SearchSongs_Mysql(setQueryforNewSong("英語"));
currentPage = 0;
currentSongList = yingWenSongs2;
totalPages = (int)Math.Ceiling((double)yingWenSongs2.Count / itemsPerPage);
multiPagePanel.currentPageIndex = 0;
multiPagePanel.LoadSongs(currentSongList);
multiPagePanel.LoadSongs(yingWenSongs2);
}
}
}

View File

@ -11,21 +11,13 @@ namespace DualScreenDemo
riYuButtonNewSong.BackgroundImage = riYuNewSongActiveBackground;
hanYuButtonNewSong.BackgroundImage = hanYuNewSongNormalBackground;
int songLimit = ReadNewSongLimit();
/*riYuSongs2 = allSongs.Where(song => song.Category == "日語")
.OrderByDescending(song => song.AddedTime)
.Take(songLimit)
.ToList();*/
string query = setQueryforNewSong("日語");
var riYuSongs2 = SearchSongs_Mysql(query);
var riYuSongs2 = SearchSongs_Mysql(setQueryforNewSong("日語"));
currentPage = 0;
currentSongList = riYuSongs2;
totalPages = (int)Math.Ceiling((double)riYuSongs2.Count / itemsPerPage);
multiPagePanel.currentPageIndex = 0;
multiPagePanel.LoadSongs(currentSongList);
multiPagePanel.LoadSongs(riYuSongs2);
}
}
}

View File

@ -11,21 +11,13 @@ namespace DualScreenDemo
riYuButtonNewSong.BackgroundImage = riYuNewSongNormalBackground;
hanYuButtonNewSong.BackgroundImage = hanYuNewSongActiveBackground;
int songLimit = ReadNewSongLimit();
/*hanYuSongs2 = allSongs.Where(song => song.Category == "韓語")
.OrderByDescending(song => song.AddedTime)
.Take(songLimit)
.ToList();*/
string query = setQueryforNewSong("韓語");
var hanYuSongs2 = SearchSongs_Mysql(query);
var hanYuSongs2 = SearchSongs_Mysql(setQueryforNewSong("韓語"));
currentPage = 0;
currentSongList = hanYuSongs2;
totalPages = (int)Math.Ceiling((double)hanYuSongs2.Count / itemsPerPage);
multiPagePanel.currentPageIndex = 0;
multiPagePanel.LoadSongs(currentSongList);
multiPagePanel.LoadSongs(hanYuSongs2);
}
}
}

View File

@ -11,21 +11,13 @@ namespace DualScreenDemo
riYuButtonNewSong.BackgroundImage = riYuNewSongNormalBackground;
hanYuButtonNewSong.BackgroundImage = hanYuNewSongNormalBackground;
int songLimit = ReadNewSongLimit();
/*taiYuSongs2 = allSongs.Where(song => song.Category == "台語")
.OrderByDescending(song => song.AddedTime)
.Take(songLimit)
.ToList();*/
string query = setQueryforNewSong("台語");
var taiYuSongs2 = SearchSongs_Mysql(query);
var taiYuSongs2 = SearchSongs_Mysql(setQueryforNewSong("台語"));
currentPage = 0;
currentSongList = taiYuSongs2;
totalPages = (int)Math.Ceiling((double)taiYuSongs2.Count / itemsPerPage);
multiPagePanel.currentPageIndex = 0;
multiPagePanel.LoadSongs(currentSongList);
multiPagePanel.LoadSongs(taiYuSongs2);
}
}
}

View File

@ -74,28 +74,6 @@ namespace DualScreenDemo
}
}
private List<Image> LoadPromotionsImages()
{
List<Image> images = new List<Image>();
string newsFolderPath = Path.Combine(Application.StartupPath, "news");
string[] imageFiles = Directory.GetFiles(newsFolderPath, "*.jpg");
foreach (string filePath in imageFiles)
{
try
{
images.Add(Image.FromFile(filePath));
}
catch (Exception ex)
{
Console.WriteLine("Error loading image: " + filePath + ". Exception: " + ex.Message);
}
}
return images;
}
private void promotionsButton_Click(object sender, EventArgs e)
{
promotionsAndMenuPanel.currentPageIndex=0;
@ -137,27 +115,16 @@ namespace DualScreenDemo
private void PreviousPromotionButton_Click(object sender, EventArgs e)
{
promotionsAndMenuPanel.LoadPreviousPage();
}
private void NextPromotionButton_Click(object sender, EventArgs e)
{
promotionsAndMenuPanel.LoadNextPage();
}
private void ClosePromotionsButton_Click(object sender, EventArgs e)
{
promotionsAndMenuPanel.Visible = false;
previousPromotionButton.Visible = false;
nextPromotionButton.Visible = false;

View File

@ -1,4 +1,8 @@
using System;
using System.Collections.Generic;
using System.Drawing;
using System.IO;
namespace DualScreenDemo
{
public partial class PrimaryForm
@ -11,32 +15,50 @@ namespace DualScreenDemo
ResizeAndPositionControl(promotionsAndMenuPanel, 0, 0, 1440, 900);
this.Controls.Add(promotionsAndMenuPanel);
promotions = LoadPromotionsImages();
menu = LoadMenuImages();
promotions = LoadImagesFromFolder("news");
menu = LoadImagesFromFolder("foods");
}
private List<Image> LoadMenuImages()
{
List<Image> images = new List<Image>();
string foodsFolderPath = Path.Combine(Application.StartupPath, "foods");
string[] imageFiles = Directory.GetFiles(foodsFolderPath, "*.jpg");
/// <summary>
/// 從指定資料夾載入所有 .jpg 圖片
/// </summary>
private List<Image> LoadImagesFromFolder(string folderName)
{
List<Image> images = new();
string folderPath = Path.Combine(Utils.Env.KtvPath, folderName);
if (!Directory.Exists(folderPath))
{
Console.WriteLine($" 找不到遠端資料夾:{folderPath}");
return images;
}
string[] imageFiles = Directory.GetFiles(folderPath, "*.jpg");
foreach (string filePath in imageFiles)
{
try
{
images.Add(Image.FromFile(filePath));
using var fs = new FileStream(filePath, FileMode.Open, FileAccess.Read);
images.Add(Image.FromStream(new MemoryStream(ReadFully(fs))));
}
catch (Exception ex)
{
Console.WriteLine("Error loading image: " + filePath + ". Exception: " + ex.Message);
Console.WriteLine($"載入圖片失敗:{filePath},原因:{ex.Message}");
}
}
return images;
}
/// <summary>
/// 將 Stream 轉為 byte[],用於避免 Image 檔案鎖定
/// </summary>
private byte[] ReadFully(Stream input)
{
using MemoryStream ms = new();
input.CopyTo(ms);
return ms.ToArray();
}
}
}
}

View File

@ -6,40 +6,9 @@ using System.Diagnostics;
namespace DualScreenDemo{
public partial class PrimaryForm
{
private static string connectionStringfortest = "";
public static void LoadConnectionStringFromFile(string filePath)
{
filePath = Path.Combine(Application.StartupPath, "txt", filePath);
try
{
if (File.Exists(filePath))
{
string fileContent = File.ReadAllText(filePath).Trim();
if (!string.IsNullOrWhiteSpace(fileContent))
{
connectionStringfortest = fileContent;
Console.WriteLine("連線字串載入成功。");
}
else
{
Console.WriteLine("檔案內容為空。");
}
}
else
{
Console.WriteLine("找不到指定的檔案:" + filePath);
}
}
catch (Exception ex)
{
Console.WriteLine("讀取檔案時發生錯誤:" + ex.Message);
}
}
private static string GetConnectionString()
{
return connectionStringfortest;
return $"Server={Utils.Env.Get("DBServer", "localhost")};Port={Utils.Env.Get("DBPort", "3306")};Database={Utils.Env.Get("Database", "test")};User={Utils.Env.Get("DBUser", "root")};Password={Utils.Env.Get("DBPassword", "")};";
}
public bool isLoggedIn = false;
@ -55,7 +24,7 @@ namespace DualScreenDemo{
Stopwatch stopwatch = new Stopwatch();
connection.Open();
stopwatch.Start();
Console.WriteLine("MyDB 連線成功!");
@ -72,15 +41,15 @@ namespace DualScreenDemo{
string artistA = reader["artistA"].ToString();
string artistB = reader["artistB"].ToString();
string fileName = reader["song_filename"].ToString();
string songFilePathHost1 = Path.Combine(@"\\SVR01\", fileName);
string songFilePathHost2 = Path.Combine(@"\\SVR02\", fileName);
string artistASimplified = reader["artistA_simplified"].ToString();
string songFilePathHost1 = Path.Combine(@"\\SVR01\e\", fileName);
string songFilePathHost2 = Path.Combine(@"\\SVR02\e\", fileName);
string artistASimplified = reader["artistA_simplified"].ToString();
string artistBSimplified = reader["artistB_simplified"].ToString();
string songSimplified = reader["song_simplified"].ToString();
int humanVoice = Convert.ToInt32(reader["vocal"]);
searchResults.Add(new SongData(
songNumber, song, artistA, artistB,fileName,
songNumber, song, artistA, artistB, fileName,
songFilePathHost1, songFilePathHost2,
artistASimplified, artistBSimplified,
songSimplified, humanVoice
@ -118,8 +87,8 @@ namespace DualScreenDemo{
while (reader.Read())
{
string artist = reader["name"].ToString();
string artistSimplified = reader ["simplified"].ToString();
searchResults.Add(new Artist(artist,artistSimplified));
string artistSimplified = reader["simplified"].ToString();
searchResults.Add(new Artist(artist, artistSimplified));
}
}
}
@ -140,7 +109,7 @@ namespace DualScreenDemo{
string connectionString = GetConnectionString();
using (var connection = new MySqlConnection(connectionString))
{
connection.Open();
Console.WriteLine("MyDB 連線成功!");
@ -168,7 +137,7 @@ namespace DualScreenDemo{
string connectionString = GetConnectionString();
using (var connection = new MySqlConnection(connectionString))
{
connection.Open();
Console.WriteLine("MyDB 連線成功!");
@ -254,7 +223,7 @@ namespace DualScreenDemo{
string connectionString = GetConnectionString();
using (var connection = new MySqlConnection(connectionString))
{
connection.Open();
Console.WriteLine("MyDB 連線成功!");

View File

@ -28,14 +28,32 @@ namespace DualScreenDemo
private void ConfigureImageButton(Button button, int posX, int posY, int width, int height,
string imagePath, EventHandler clickEventHandler)
{
Bitmap image = new Bitmap(imagePath);
button.SetBounds(posX, posY, image.Width, image.Height);
// 你的基準解析度(設計時以這個為標準)
const float baseWidth = 1920f;
const float baseHeight = 1080f;
// 實際螢幕解析度
float actualWidth = Screen.PrimaryScreen.Bounds.Width;
float actualHeight = Screen.PrimaryScreen.Bounds.Height;
// 計算縮放比例
float scaleX = actualWidth / baseWidth;
float scaleY = actualHeight / baseHeight;
// 套用縮放比例到位置與大小
int scaledX = (int)(posX * scaleX);
int scaledY = (int)(posY * scaleY);
int scaledWidth = (int)(width * scaleX);
int scaledHeight = (int)(height * scaleY);
// 載入圖片並調整按鈕尺寸
Bitmap image = new Bitmap(imagePath);
button.SetBounds(scaledX, scaledY, scaledWidth, scaledHeight);
// 載入圖片
button.BackgroundImage = image;
button.BackgroundImageLayout = ImageLayout.Stretch;
// 按鈕樣式設定
// 按鈕樣式
button.FlatStyle = FlatStyle.Flat;
button.FlatAppearance.BorderSize = 0;
button.FlatAppearance.MouseDownBackColor = Color.Transparent;
@ -165,20 +183,28 @@ namespace DualScreenDemo
if (File.Exists(imagePath))
{
// 直接載入完整圖
Bitmap image = new Bitmap(imagePath);
try
{
// 使用 Image.FromFile 載入後 clone避免檔案 lock & GDI 錯誤
using (Image original = Image.FromFile(imagePath))
{
Bitmap image = new Bitmap(original);
pictureBoxSceneSoundEffects.Image = image;
}
// 顯示在 PictureBox 上
pictureBoxSceneSoundEffects.Image = image;
ResizeAndPositionPictureBox(pictureBoxSceneSoundEffects, 850, 450,
pictureBoxSceneSoundEffects.Image.Width, pictureBoxSceneSoundEffects.Image.Height);
// 設定 PictureBox 的大小與位置(依你的需要調整)
ResizeAndPositionPictureBox(pictureBoxSceneSoundEffects, 850, 450, image.Width , image.Height);
pictureBoxSceneSoundEffects.Visible = true;
pictureBoxSceneSoundEffects.Visible = true;
}
catch (Exception ex)
{
Console.WriteLine("圖片載入失敗:" + ex.Message, "錯誤", MessageBoxButtons.OK, MessageBoxIcon.Error);
}
}
else
{
Console.WriteLine("圖片檔案不存在:" + imagePath);
Console.WriteLine("圖片檔案不存在:" + imagePath, "錯誤", MessageBoxButtons.OK, MessageBoxIcon.Warning);
}
}

View File

@ -239,7 +239,6 @@ namespace DualScreenDemo
multiPagePanel.PageIndexChanged += HandlePageChanged;
// 添加 Load 事件處理
this.Load += PrimaryForm_Load;
LoadConnectionStringFromFile("test.env");
}
public bool IsAppResponsive()
@ -504,7 +503,7 @@ namespace DualScreenDemo
string selectedTheme = ReadSelectedThemePath();
if (!string.IsNullOrEmpty(selectedTheme))
{
string backgroundImagePath = Path.Combine(Application.StartupPath, "themes\\superstar\\555009.jpg");
string backgroundImagePath = Path.Combine(Utils.Env.KtvPath, "themes","superstar","555009.jpg");
try
{
using (Image originalImage = Image.FromFile(backgroundImagePath))
@ -1554,23 +1553,10 @@ namespace DualScreenDemo
try
{
// 1. 檢查是否能連接到 SVR01
string serverVideoPath = @"\\SVR01\video";
string serverVideoPath2 = @"\\SVR02\video";
string selectedServerPath = null;
string serverVideoPath = Path.Combine(Utils.Env.KtvPath, "video");
string localVideoPath = @"D:\video";
if (Directory.Exists(serverVideoPath))
{
selectedServerPath = serverVideoPath;
Console.WriteLine("已連接到 SVR01");
}
else if (Directory.Exists(serverVideoPath2))
{
selectedServerPath = serverVideoPath2;
Console.WriteLine("已連接到 SVR02");
}
else
if (!Directory.Exists(serverVideoPath))
{
Console.WriteLine("未連接到 SVR使用本地影片");
LoadLocalVideoFiles();
@ -1584,7 +1570,7 @@ namespace DualScreenDemo
}
// 獲取服務器和本地的所有文件
var serverFiles = Directory.GetFiles(selectedServerPath, "*.mpg")
var serverFiles = Directory.GetFiles(serverVideoPath, "*.mpg")
.Select(f => new FileInfo(f))
.ToDictionary(f => f.Name, f => f);
@ -2351,13 +2337,14 @@ namespace DualScreenDemo
SendCommandThroughSerialPort("a2 53 a4");
// 显示提示信息
OverlayForm.MainForm.ShowServiceBellLabel();
OverlayForm.MainForm.HideAllLabels();
OverlayForm.MainForm.ShowServiceBell();
// 延迟3秒
await Task.Delay(3000);
// 隐藏提示信息
OverlayForm.MainForm.HideServiceBellLabel();
//OverlayForm.MainForm.HideServiceBellLabel();
isWaiting = false;
}
@ -2565,46 +2552,14 @@ namespace DualScreenDemo
{
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}");
}
if (Program.RoomState.Equals("CLOSE"))
{
ShowSendOffScreen();
}
// 確保所有控件都已初始化完成後,再觸發熱門排行按鈕點擊
}
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)
{
if (isOnOrderedSongsPage)

View File

@ -2,8 +2,7 @@ using System.IO;
using Microsoft.Win32;
using System.Diagnostics;
using DBObj;
using DataCheck;
using HeartbeatSender;
namespace DualScreenDemo
{
public static class Program
@ -13,59 +12,30 @@ namespace DualScreenDemo
//internal static ArtistManager artistManager;
internal static SerialPortManager serialPortManager;
private static PrimaryForm primaryForm; // 儲存實例的參考
public static string RoomState = Utils.Env.Get("RoomStates", "CLOSE");
[STAThread]
static void Main()
{
Console.WriteLine("隱藏滑鼠游標");
Cursor.Hide();
Console.WriteLine("Server V.1.1.0 20250701");
if(Utils.Env.GetBool("IsCursor", true))Cursor.Hide();
AppDomain.CurrentDomain.ProcessExit += (s, e) =>
{
Cursor.Show();
};
Console.WriteLine("正在喚醒SVR裝置(每3分鐘呼叫一次)...");
_ = Task.Run(async () =>
{
while (true)
{
_ = Directory.Exists(@"\\svr01\video");
_ = Directory.Exists(@"\\svr02\video");
await Task.Delay(180000); // 每3min送一次
}
});
//Console.WriteLine("正在喚醒SVR裝置(每3分鐘呼叫一次)...");
//_ = Task.Run(async () =>
// {
// while (true)
// {
// _ = Directory.Exists(@"\\svr01\e\video");
// _ = Directory.Exists(@"\\svr02\e\video");
// await Task.Delay(180000); // 每3min送一次
// }
// });
// Console.WriteLine("正在與中控取得聯繫...");
var sender = new HeartbeatSender.heartbeatSender();
// 同步呼叫非同步登入取得 token
bool loginSuccess = sender.LoginAndGetTokenAsync().GetAwaiter().GetResult();
if (loginSuccess)
{
// 先送一次心跳 (同步呼叫)
sender.SendHeartbeatAsync().GetAwaiter().GetResult();
// 背景持續每5分鐘送心跳
_ = Task.Run(async () =>
{
while (true)
{
await sender.SendHeartbeatAsync();
await Task.Delay(300000); // 每5min送一次
}
});
Console.WriteLine("正在發送心跳中...");
}
else
{
Console.WriteLine("登入失敗,無法送出心跳");
}
//之後需要做添加同步菜單+酒單+背景圖(若圖規格有做正規化)
// DataCheck.cs 有預寫好相關流程函式
Console.WriteLine("更新菜單和酒單...");
dataCheck Checkprocess = new dataCheck();
var sender = new heartbeatSender();
try
{
@ -102,27 +72,13 @@ namespace DualScreenDemo
// 創建主窗體
primaryForm = new PrimaryForm();
//primaryForm.allSongs = songListManager.AllSongs;
//primaryForm.allArtists = artistManager.AllArtists;
primaryForm.StartPosition = FormStartPosition.Manual;
primaryForm.Location = new Point(0, 0);
primaryForm.Size = new Size(Screen.PrimaryScreen.Bounds.Width, Screen.PrimaryScreen.Bounds.Height);
// 在完整初始化後檢查狀態
string stateFilePath = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "txt", "states.txt");
bool isClosedState = File.Exists(stateFilePath) &&
File.ReadAllText(stateFilePath).Trim().Equals("CLOSE", StringComparison.OrdinalIgnoreCase);
InitializeSecondaryScreen();
// 使用 Shown 事件來確保窗體完全加載後再顯示送客畫面
if (isClosedState)
{
primaryForm.Shown += (s, e) =>
{
primaryForm.ShowSendOffScreen();
};
}
WatchDog _watchDog = new WatchDog(
() => VideoPlayerForm.Instance.GetCurrentVideoStatus(),
() => primaryForm.IsAppResponsive()
@ -133,7 +89,6 @@ namespace DualScreenDemo
{
_watchDog.Stop();
};
primaryForm.Show();
Application.Run(primaryForm);
@ -148,7 +103,7 @@ namespace DualScreenDemo
{
SystemEvents.DisplaySettingsChanged -= OnDisplaySettingsChanged;
}
}
}
private static bool IsUrlAclExists(string url)

View File

@ -89,28 +89,6 @@ namespace DualScreenDemo
try {
string stateFilePath = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "txt", "states.txt");
string initialState = ReadStateFile(stateFilePath);
/*
if (initialState.Equals("CLOSE", StringComparison.OrdinalIgnoreCase))
{
_ = SafeInvoke(PrimaryForm.Instance, () =>
{
try {
foreach (Control ctrl in PrimaryForm.Instance.Controls)
{
ctrl.Enabled = false;
}
PrimaryForm.Instance.ShowSendOffScreen();
}
catch (Exception ex) {
Console.WriteLine($"顯示送客畫面時發生錯誤: {ex.Message}");
}
});
} */
while (true)
{
Console.WriteLine("Waiting for connections...");
@ -201,7 +179,7 @@ namespace DualScreenDemo
}
});
UpdateStateFile(stateFilePath, "CLOSE");
Program.RoomState = "CLOSE";
byte[] okResponse = Encoding.UTF8.GetBytes("OK\n");
stream.Write(okResponse, 0, okResponse.Length);
@ -217,7 +195,7 @@ namespace DualScreenDemo
VideoPlayerForm.Instance.PlayPublicPlaylist();
PrimaryForm.currentSongIndexInHistory = -1;
PrimaryForm.Instance.HotPlayButton_Click(null, EventArgs.Empty);
UpdateStateFile(stateFilePath, "OPEN");
Program.RoomState = "OPEN";
PrimaryForm.Instance.HideSendOffScreen();
@ -236,7 +214,7 @@ namespace DualScreenDemo
});
// 更新狀態檔案(可選,若你要記錄狀態)
UpdateStateFile(stateFilePath, "PAUSE");
Program.RoomState = "PAUSE";
byte[] okResponse = Encoding.UTF8.GetBytes("OK\n");
stream.Write(okResponse, 0, okResponse.Length);
@ -266,12 +244,18 @@ namespace DualScreenDemo
OverlayForm.MainForm.UpdateMarqueeTextSecondLine(marqueeMessage);
}
});
byte[] okResponse = Encoding.UTF8.GetBytes("OK\n");
stream.Write(okResponse, 0, okResponse.Length);
continue;
}
/*
if (request.Trim().Equals("exit", StringComparison.OrdinalIgnoreCase))
{
break;
}
*/
}
Console.WriteLine("Connection closed.");
@ -312,40 +296,5 @@ namespace DualScreenDemo
string hostName = Dns.GetHostName();
return hostName.Length > 3 ? hostName.Substring(hostName.Length - 3) : hostName;
}
private string ReadStateFile(string filePath)
{
try
{
if (File.Exists(filePath))
{
string state = File.ReadAllText(filePath).Trim();
Console.WriteLine($"✅ State file read: {filePath} -> {state}");
return state;
}
else
{
Console.WriteLine("⚠️ State file not found. Creating new file with default state: OPEN");
UpdateStateFile(filePath, "OPEN");
return "OPEN";
}
}
catch (Exception ex)
{
Console.WriteLine($"❌ Failed to read state file: {ex.Message}");
return "OPEN"; // 默認為 OPEN
}
}
private void UpdateStateFile(string filePath, string state)
{
try
{
File.WriteAllText(filePath, state);
Console.WriteLine($"✅ State file updated: {filePath} -> {state}");
}
catch (Exception ex)
{
Console.WriteLine($"❌ Failed to update state file: {ex.Message}");
}
}
}
}

View File

@ -1596,14 +1596,15 @@ namespace DualScreenDemo
{
streamSelect.Enable(audioTrack1, AMStreamSelectEnableFlags.Enable);
isVocalRemoved = false;
}
//OverlayForm.MainForm.ShowOriginalSongLabel();
}
string labelText = isVocalRemoved ? "無人聲" : "有人聲";
// 显示标签
OverlayForm.MainForm.ShowOriginalSongLabel(labelText);
await Task.Delay(3000);
// 修改成新標籤
OverlayForm.MainForm.HideAllLabels();
OverlayForm.MainForm.ShowOriginalLabel(labelText);
// await Task.Delay(3000);
// 隐藏标签
OverlayForm.MainForm.HideOriginalSongLabel();
// OverlayForm.MainForm.HideOriginalSongLabel();
}
}
}

View File

@ -71,16 +71,6 @@
</Reference>
</ItemGroup>
<ItemGroup>
<!-- 複製資料夾 foods -->
<Content Include="foods\**\*.*">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
<!-- 複製資料夾 db -->
<Content Include="db\**\*.*">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
<!-- 複製資料夾 txt -->
<Content Include="txt\**\*.*">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
@ -90,11 +80,6 @@
<Content Include="themes\**\*.*">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
<!-- 複製資料夾 news -->
<Content Include="news\**\*.*">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
<!-- 複製資料夾 sounds -->
<Content Include="sounds\**\*.*">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>