Compare commits

..

No commits in common. "107639f4c9feea20465ead38b1c3f11dc4a543b1" and "3d255da6750414513f82d65db4a29cde230f0838" have entirely different histories.

15 changed files with 263 additions and 719 deletions

View File

@ -26,9 +26,10 @@ namespace DualScreenDemo
/// 遙控器接收資料 /// 遙控器接收資料
/// </summary> /// </summary>
public async Task ProcessData(string indata) public async Task ProcessData(string indata)
{ AddToHistory(indata); {
AddToHistory(indata);
// 遙控器測試 // 遙控器測試
// Console.WriteLine($"[{DateTime.Now:HH:mm:ss.fff}] 遙控器: {indata}"); Console.WriteLine("遙控器: " + indata);
switch (indata) switch (indata)
{ {
case "A261A4": // 輸入 case "A261A4": // 輸入
@ -69,12 +70,12 @@ namespace DualScreenDemo
// 原唱 // 原唱
case "A26CA4": case "A26CA4":
Console.WriteLine("ToggleVocalRemoval Invoked"); Console.WriteLine("ToggleVocalRemoval Invoked");
SafeInvokeAction("A26CA4",() => VideoPlayerForm.Instance.ToggleVocalRemoval()); InvokeAction(() => VideoPlayerForm.Instance.ToggleVocalRemoval());
SafeInvokeAction("A26CA4",() => OverlayForm.MainForm.ShowOriginalSongLabel()); InvokeAction(() => OverlayForm.MainForm.ShowOriginalSongLabel());
break; break;
// 導唱 // 導唱
case "A26EA4": case "A26EA4":
SafeInvokeAction("A26EA4",() => VideoPlayerForm.Instance.ToggleVocalRemoval()); InvokeAction(() => VideoPlayerForm.Instance.ToggleVocalRemoval());
break; break;
case "A26DA4": case "A26DA4":
PauseOrResumeSong(); PauseOrResumeSong();
@ -87,44 +88,44 @@ namespace DualScreenDemo
HandleArtistAnnouncements(); HandleArtistAnnouncements();
break; break;
case "A2B3A4": case "A2B3A4":
SafeInvokeAction("A2B3A4",() => OverlayForm.MainForm.HideAllLabels()); InvokeAction(() => OverlayForm.MainForm.HideAllLabels());
SafeInvokeAction("A2B3A4",() => OverlayForm.MainForm.ShowVolumeUpLabel()); InvokeAction(() => OverlayForm.MainForm.ShowVolumeUpLabel());
break; break;
case "A2B4A4": case "A2B4A4":
SafeInvokeAction("A2B4A4",() => OverlayForm.MainForm.HideAllLabels()); InvokeAction(() => OverlayForm.MainForm.HideAllLabels());
SafeInvokeAction("A2B4A4",() => OverlayForm.MainForm.ShowVolumeDownLabel()); InvokeAction(() => OverlayForm.MainForm.ShowVolumeDownLabel());
break; break;
case "A2B5A4": case "A2B5A4":
SafeInvokeAction("A2B5A4",() => OverlayForm.MainForm.HideAllLabels()); InvokeAction(() => OverlayForm.MainForm.HideAllLabels());
SafeInvokeAction("A2B5A4",() => OverlayForm.MainForm.ShowMicUpLabel()); InvokeAction(() => OverlayForm.MainForm.ShowMicUpLabel());
break; break;
case "A2B6A4": case "A2B6A4":
SafeInvokeAction("A2B6A4",() => OverlayForm.MainForm.HideAllLabels()); InvokeAction(() => OverlayForm.MainForm.HideAllLabels());
SafeInvokeAction("A2B6A4",() => OverlayForm.MainForm.ShowMicDownLabel()); InvokeAction(() => OverlayForm.MainForm.ShowMicDownLabel());
break; break;
case "A2C2A4": case "A2C2A4":
SafeInvokeAction("A2C2A4",() => OverlayForm.MainForm.HidemicLabels()); InvokeAction(() => OverlayForm.MainForm.HidemicLabels());
SafeInvokeAction("A2C2A4",() => OverlayForm.MainForm.ShowStandardLabel()); InvokeAction(() => OverlayForm.MainForm.ShowStandardLabel());
break; break;
case "A2C3A4": case "A2C3A4":
SafeInvokeAction("A2C3A4",() => OverlayForm.MainForm.HidemicLabels()); InvokeAction(() => OverlayForm.MainForm.HidemicLabels());
SafeInvokeAction("A2C3A4",() => OverlayForm.MainForm.ShowProfessionalLabel()); InvokeAction(() => OverlayForm.MainForm.ShowProfessionalLabel());
break; break;
case "A2C4A4": case "A2C4A4":
SafeInvokeAction("A2C4A4",() => OverlayForm.MainForm.HidemicLabels()); InvokeAction(() => OverlayForm.MainForm.HidemicLabels());
SafeInvokeAction("A2C4A4",() => OverlayForm.MainForm.ShowSquareLabel()); InvokeAction(() => OverlayForm.MainForm.ShowSquareLabel());
break; break;
case "A2C1A4": case "A2C1A4":
SafeInvokeAction("A2C1A4",() => OverlayForm.MainForm.HidemicLabels()); InvokeAction(() => OverlayForm.MainForm.HidemicLabels());
SafeInvokeAction("A2C1A4",() => OverlayForm.MainForm.ShowSingDownLabel()); InvokeAction(() => OverlayForm.MainForm.ShowSingDownLabel());
break; break;
case "A2D5A4": case "A2D5A4":
SafeInvokeAction("A2D5A4",() => OverlayForm.MainForm.HideAllLabels()); InvokeAction(() => OverlayForm.MainForm.HideAllLabels());
SafeInvokeAction("A2D5A4",() => OverlayForm.MainForm.ShowBrightLabel()); InvokeAction(() => OverlayForm.MainForm.ShowBrightLabel());
break; break;
case "A2D7A4": case "A2D7A4":
SafeInvokeAction("A2D7A4",() => OverlayForm.MainForm.HideAllLabels()); InvokeAction(() => OverlayForm.MainForm.HideAllLabels());
SafeInvokeAction("A2D7A4",() => OverlayForm.MainForm.ShowRomanticLabel()); InvokeAction(() => OverlayForm.MainForm.ShowRomanticLabel());
break; break;
/* case "A27CA4": /* case "A27CA4":
InvokeAction(() => OverlayForm.MainForm.ShowMaleKeyLabel()); InvokeAction(() => OverlayForm.MainForm.ShowMaleKeyLabel());
@ -133,52 +134,52 @@ namespace DualScreenDemo
InvokeAction(() => OverlayForm.MainForm.ShowFemaleKeyLabel()); InvokeAction(() => OverlayForm.MainForm.ShowFemaleKeyLabel());
break;*/ break;*/
case "A2D6A4": case "A2D6A4":
SafeInvokeAction("A2D6A4",() => OverlayForm.MainForm.HideAllLabels()); InvokeAction(() => OverlayForm.MainForm.HideAllLabels());
SafeInvokeAction("A2D6A4",() => OverlayForm.MainForm.ShowSoftLabel()); InvokeAction(() => OverlayForm.MainForm.ShowSoftLabel());
break; break;
case "A2D8A4": case "A2D8A4":
SafeInvokeAction("A2D8A4",() => OverlayForm.MainForm.HideAllLabels()); InvokeAction(() => OverlayForm.MainForm.HideAllLabels());
SafeInvokeAction("A2D8A4",() => OverlayForm.MainForm.ShowDynamicLabel()); InvokeAction(() => OverlayForm.MainForm.ShowDynamicLabel());
break; break;
case "A275A4": case "A275A4":
SafeInvokeAction("A275A4",() => OverlayForm.MainForm.HideAllLabels()); InvokeAction(() => OverlayForm.MainForm.HideAllLabels());
SafeInvokeAction("A275A4",() => OverlayForm.MainForm.ShowTintLabel()); InvokeAction(() => OverlayForm.MainForm.ShowTintLabel());
break; break;
case "A283A4": case "A283A4":
SafeInvokeAction("A283A4",() => OverlayForm.MainForm.HideAllLabels()); InvokeAction(() => OverlayForm.MainForm.HideAllLabels());
SafeInvokeAction("A283A4",() => OverlayForm.MainForm.ShowKeyUpLabel("↑升4調")); InvokeAction(() => OverlayForm.MainForm.ShowKeyUpLabel("↑升4調"));
break; break;
case "A282A4": case "A282A4":
SafeInvokeAction("A282A4",() => OverlayForm.MainForm.HideAllLabels()); InvokeAction(() => OverlayForm.MainForm.HideAllLabels());
SafeInvokeAction("A282A4",() => OverlayForm.MainForm.ShowKeyUpLabel("↑升3調")); InvokeAction(() => OverlayForm.MainForm.ShowKeyUpLabel("↑升3調"));
break; break;
case "A281A4": case "A281A4":
SafeInvokeAction("A281A4",() => OverlayForm.MainForm.HideAllLabels()); InvokeAction(() => OverlayForm.MainForm.HideAllLabels());
SafeInvokeAction("A281A4",() => OverlayForm.MainForm.ShowKeyUpLabel("↑升2調")); InvokeAction(() => OverlayForm.MainForm.ShowKeyUpLabel("↑升2調"));
break; break;
case "A280A4": case "A280A4":
SafeInvokeAction("A280A4",() => OverlayForm.MainForm.HideAllLabels()); InvokeAction(() => OverlayForm.MainForm.HideAllLabels());
SafeInvokeAction("A280A4",() => OverlayForm.MainForm.ShowKeyUpLabel("↑升1調")); InvokeAction(() => OverlayForm.MainForm.ShowKeyUpLabel("↑升1調"));
break; break;
case "A27FA4": case "A27FA4":
SafeInvokeAction("A27FA4",() => OverlayForm.MainForm.HideAllLabels()); InvokeAction(() => OverlayForm.MainForm.HideAllLabels());
SafeInvokeAction("A27FA4",() => OverlayForm.MainForm.ShowStandardKeyLabel()); InvokeAction(() => OverlayForm.MainForm.ShowStandardKeyLabel());
break; break;
case "A27EA4": case "A27EA4":
SafeInvokeAction("A27EA4",() => OverlayForm.MainForm.HideAllLabels()); InvokeAction(() => OverlayForm.MainForm.HideAllLabels());
SafeInvokeAction("A27EA4",() => OverlayForm.MainForm.ShowKeyDownLabel("↓降1調")); InvokeAction(() => OverlayForm.MainForm.ShowKeyDownLabel("↓降1調"));
break; break;
case "A27DA4": case "A27DA4":
SafeInvokeAction("A27EA4",() => OverlayForm.MainForm.HideAllLabels()); InvokeAction(() => OverlayForm.MainForm.HideAllLabels());
SafeInvokeAction("A27EA4",() => OverlayForm.MainForm.ShowKeyDownLabel("↓降2調")); InvokeAction(() => OverlayForm.MainForm.ShowKeyDownLabel("↓降2調"));
break; break;
case "A27CA4": case "A27CA4":
SafeInvokeAction("A27CA4",() => OverlayForm.MainForm.HideAllLabels()); InvokeAction(() => OverlayForm.MainForm.HideAllLabels());
SafeInvokeAction("A27CA4",() => OverlayForm.MainForm.ShowKeyDownLabel("↓降3調")); InvokeAction(() => OverlayForm.MainForm.ShowKeyDownLabel("↓降3調"));
break; break;
case "A27BA4": case "A27BA4":
SafeInvokeAction("A27BA4",() => OverlayForm.MainForm.HideAllLabels()); InvokeAction(() => OverlayForm.MainForm.HideAllLabels());
SafeInvokeAction("A27BA4",() => OverlayForm.MainForm.ShowKeyDownLabel("↓降4調")); InvokeAction(() => OverlayForm.MainForm.ShowKeyDownLabel("↓降4調"));
break; break;
default: default:
if (Regex.IsMatch(indata, @"^A23\d+A4$")) if (Regex.IsMatch(indata, @"^A23\d+A4$"))
@ -187,31 +188,6 @@ namespace DualScreenDemo
} }
break; break;
} }
}
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)
{
OverlayForm.MainForm.BeginInvoke(action);
}
else
{
action();
}
} }
private void AddToHistory(string indata) private void AddToHistory(string indata)

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}");
}
}
}
}
}
}

View File

@ -1,10 +0,0 @@
namespace DualScreenDemo.Shared
{
public class VideoStatus
{
public bool IsGraphOk { get; set; }
public string LastError { get; set; }
public double PositionSeconds { get; set; }
public string PlayState { get; set; }
}
}

View File

@ -168,10 +168,7 @@ namespace HeartbeatSender
private float GetTotalMemoryInMB() private float GetTotalMemoryInMB()
{ {
var computerInfo = new ComputerInfo(); var computerInfo = new ComputerInfo();
var totalMB = computerInfo.TotalPhysicalMemory / (1024f); return computerInfo.TotalPhysicalMemory / (1024f * 1024f); // 轉 MB
var availableMB = computerInfo.AvailablePhysicalMemory / (1024f);
var usedMB = totalMB - availableMB;
return usedMB; // 轉 MB
} }
private float GetDiskTotalSizeInGB(string driveLetter = "C") private float GetDiskTotalSizeInGB(string driveLetter = "C")
{ {

View File

@ -40,10 +40,9 @@ namespace DualScreenDemo
string randomFolderName = CreateRandomFolderAndRedirectHTML(baseDirectory); string randomFolderName = CreateRandomFolderAndRedirectHTML(baseDirectory);
randomFolderPath = randomFolderName; randomFolderPath = randomFolderName;
// 安裝包更新
string localAddress = GetLocalIPAddress(); string localAddress = GetLocalIPAddress();
string externalAddress = File.Exists(@"\\JLDKTV\txt\ip.txt") string externalAddress = File.Exists(@"\\svr01\txt\ip.txt")
? File.ReadAllText(@"\\JLDKTV\txt\ip.txt").Trim() ? File.ReadAllText(@"\\svr01\txt\ip.txt").Trim()
: ""; : "";
_listener = new HttpListener(); _listener = new HttpListener();
@ -430,48 +429,34 @@ namespace DualScreenDemo
case "pause": case "pause":
// 执行暂停操作 // 执行暂停操作
if (VideoPlayerForm.Instance.isPaused) if (VideoPlayerForm.Instance.isPaused)
{
PrimaryForm.Instance.Invoke(new System.Action(() =>
{ {
PrimaryForm.Instance.videoPlayerForm.Play(); PrimaryForm.Instance.videoPlayerForm.Play();
PrimaryForm.Instance.pauseButton.Visible = true; PrimaryForm.Instance.pauseButton.Visible = true;
PrimaryForm.Instance.playButton.Visible = false; PrimaryForm.Instance.playButton.Visible = false;
PrimaryForm.Instance.syncPauseButton.Visible = true; PrimaryForm.Instance.syncPauseButton.Visible = true;
PrimaryForm.Instance.syncPlayButton.Visible = false; PrimaryForm.Instance.syncPlayButton.Visible = false;
}));
} }
else else
{
PrimaryForm.Instance.Invoke(new System.Action(() =>
{ {
PrimaryForm.Instance.videoPlayerForm.Pause(); PrimaryForm.Instance.videoPlayerForm.Pause();
PrimaryForm.Instance.pauseButton.Visible = false; PrimaryForm.Instance.pauseButton.Visible = false;
PrimaryForm.Instance.playButton.Visible = true; PrimaryForm.Instance.playButton.Visible = true;
PrimaryForm.Instance.syncPauseButton.Visible = false; PrimaryForm.Instance.syncPauseButton.Visible = false;
PrimaryForm.Instance.syncPlayButton.Visible = true; PrimaryForm.Instance.syncPlayButton.Visible = true;
}));
} }
break; break;
case "volume_up": case "volume_up":
// 执行音量增大操作 // 执行音量增大操作
PrimaryForm.SendCommandThroughSerialPort("a2 b3 a4"); PrimaryForm.SendCommandThroughSerialPort("a2 b3 a4");
OverlayForm.MainForm.Invoke(new System.Action(() => OverlayForm.MainForm.Invoke(new System.Action(() => {
{
OverlayForm.MainForm.ShowVolumeUpLabel(); OverlayForm.MainForm.ShowVolumeUpLabel();
PrimaryForm.Instance.volumeUpTimer.Start();
OverlayForm.MainForm.HideAllLabels();
PrimaryForm.Instance.volumeUpTimer.Stop();
})); }));
break; break;
case "mic_up": case "mic_up":
// 执行麦克风增大操作 // 执行麦克风增大操作
PrimaryForm.SendCommandThroughSerialPort("a2 b5 a4"); PrimaryForm.SendCommandThroughSerialPort("a2 b5 a4");
OverlayForm.MainForm.Invoke(new System.Action(() => OverlayForm.MainForm.Invoke(new System.Action(() => {
{
OverlayForm.MainForm.ShowMicUpLabel(); OverlayForm.MainForm.ShowMicUpLabel();
PrimaryForm.Instance.micControlTimer.Start();
OverlayForm.MainForm.HideAllLabels();
PrimaryForm.Instance.micControlTimer.Stop();
})); }));
break; break;
case "mute": case "mute":
@ -500,23 +485,15 @@ namespace DualScreenDemo
case "volume_down": case "volume_down":
// 执行音量减小操作 // 执行音量减小操作
PrimaryForm.SendCommandThroughSerialPort("a2 b4 a4"); PrimaryForm.SendCommandThroughSerialPort("a2 b4 a4");
OverlayForm.MainForm.Invoke(new System.Action(() => OverlayForm.MainForm.Invoke(new System.Action(() => {
{
OverlayForm.MainForm.ShowVolumeDownLabel(); OverlayForm.MainForm.ShowVolumeDownLabel();
PrimaryForm.Instance.volumeDownTimer.Start();
OverlayForm.MainForm.HideAllLabels();
PrimaryForm.Instance.volumeDownTimer.Stop();
})); }));
break; break;
case "mic_down": case "mic_down":
// 执行麦克风减小操作 // 执行麦克风减小操作
PrimaryForm.SendCommandThroughSerialPort("a2 b6 a4"); PrimaryForm.SendCommandThroughSerialPort("a2 b6 a4");
OverlayForm.MainForm.Invoke(new System.Action(() => OverlayForm.MainForm.Invoke(new System.Action(() => {
{
OverlayForm.MainForm.ShowMicDownLabel(); OverlayForm.MainForm.ShowMicDownLabel();
PrimaryForm.Instance.micControlTimer.Start();
OverlayForm.MainForm.HideAllLabels();
PrimaryForm.Instance.micControlTimer.Stop();
})); }));
break; break;
case "original_song": case "original_song":

View File

@ -450,12 +450,12 @@ private static void SongDisplayTimer_Elapsed(object sender, EventArgs e)
// 如果目前不在 UI 執行緒上,必須透過 Invoke 安全執行 UI 更新邏輯 // 如果目前不在 UI 執行緒上,必須透過 Invoke 安全執行 UI 更新邏輯
Console.WriteLine("SongDisplayTimer_Tick invoked on UI thread."); Console.WriteLine("SongDisplayTimer_Tick invoked on UI thread.");
MainForm.BeginInvoke(new Action(() => MainForm.Invoke(new System.Action(() =>
{ {
if (MainForm.songDisplayLabel != null) // 清除目前歌曲的顯示標籤文字
MainForm.songDisplayLabel.Text = ""; MainForm.songDisplayLabel.Text = "";
if (MainForm.nextSongLabel != null) // 顯示下一首歌的標籤
MainForm.nextSongLabel.Visible = true; MainForm.nextSongLabel.Visible = true;
})); }));
} }

View File

@ -1,10 +1,6 @@
using System;
using System.Drawing;
using System.IO; using System.IO;
using System.Windows.Forms;
using NAudio.Wave; using NAudio.Wave;
using WMPLib; using WMPLib;
using System.Collections.Generic;
namespace DualScreenDemo namespace DualScreenDemo
{ {
@ -25,28 +21,6 @@ namespace DualScreenDemo
{ {
mediaPlayer = new WindowsMediaPlayer(); mediaPlayer = new WindowsMediaPlayer();
} }
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);
// 載入圖片
button.BackgroundImage = image;
button.BackgroundImageLayout = ImageLayout.Stretch;
// 按鈕樣式設定
button.FlatStyle = FlatStyle.Flat;
button.FlatAppearance.BorderSize = 0;
button.FlatAppearance.MouseDownBackColor = Color.Transparent;
button.FlatAppearance.MouseOverBackColor = Color.Transparent;
// 點擊事件
if (clickEventHandler != null)
button.Click += clickEventHandler;
this.Controls.Add(button);
}
private void InitializeSoundEffectButtons() private void InitializeSoundEffectButtons()
{ {
@ -55,9 +29,9 @@ namespace DualScreenDemo
{ {
Name = "constructionButton", Name = "constructionButton",
}; };
string path = Path.Combine(Application.StartupPath, @"themes\superstar\場景音效\場景音效_工地.png"); ConfigureButton(constructionButton, 876, 494, 148, 64,
ConfigureImageButton(constructionButton, 1183, 634, 148, 64, resizedNormalStateImageForSceneSoundEffects, resizedNormalStateImageForSceneSoundEffects, resizedNormalStateImageForSceneSoundEffects,
path, ConstructionButton_Click); ConstructionButton_Click);
this.Controls.Add(constructionButton); this.Controls.Add(constructionButton);
@ -65,9 +39,9 @@ namespace DualScreenDemo
{ {
Name = "marketButton", Name = "marketButton",
}; };
path = Path.Combine(Application.StartupPath, @"themes\superstar\場景音效\場景音效_市場.png"); ConfigureButton(marketButton, 1037, 495, 148, 63,
ConfigureImageButton(marketButton, 1394, 634, 148, 63, resizedNormalStateImageForSceneSoundEffects, resizedNormalStateImageForSceneSoundEffects, resizedNormalStateImageForSceneSoundEffects,
path, MarketButton_Click); MarketButton_Click);
this.Controls.Add(marketButton); this.Controls.Add(marketButton);
@ -75,9 +49,9 @@ namespace DualScreenDemo
{ {
Name = "drivingButton", Name = "drivingButton",
}; };
path = Path.Combine(Application.StartupPath, @"themes\superstar\場景音效\場景音效_開車.png"); ConfigureButton(drivingButton, 876, 570, 148, 63,
ConfigureImageButton(drivingButton, 1183, 720, 148, 63, resizedNormalStateImageForSceneSoundEffects, resizedNormalStateImageForSceneSoundEffects, resizedNormalStateImageForSceneSoundEffects,
path, DrivingButton_Click); DrivingButton_Click);
this.Controls.Add(drivingButton); this.Controls.Add(drivingButton);
@ -85,9 +59,9 @@ namespace DualScreenDemo
{ {
Name = "airportButton", Name = "airportButton",
}; };
path = Path.Combine(Application.StartupPath, @"themes\superstar\場景音效\場景音效_機場.png"); ConfigureButton(airportButton, 1037, 570, 148, 63,
ConfigureImageButton(airportButton, 1394, 720, 148, 63, resizedNormalStateImageForSceneSoundEffects, resizedNormalStateImageForSceneSoundEffects, resizedNormalStateImageForSceneSoundEffects,
path, AirportButton_Click); AirportButton_Click);
this.Controls.Add(airportButton); this.Controls.Add(airportButton);
@ -95,9 +69,9 @@ namespace DualScreenDemo
{ {
Name = "officeButton", Name = "officeButton",
}; };
path = Path.Combine(Application.StartupPath, @"themes\superstar\場景音效\場景音效_辦公室.png"); ConfigureButton(officeButton, 876, 646, 148, 64,
ConfigureImageButton(officeButton, 1183, 806, 148, 64, resizedNormalStateImageForSceneSoundEffects, resizedNormalStateImageForSceneSoundEffects, resizedNormalStateImageForSceneSoundEffects,
path, OfficeButton_Click); OfficeButton_Click);
this.Controls.Add(officeButton); this.Controls.Add(officeButton);
@ -105,9 +79,10 @@ namespace DualScreenDemo
{ {
Name = "closeButton", Name = "closeButton",
}; };
path = Path.Combine(Application.StartupPath, @"themes\superstar\場景音效\場景音效_關閉.png");
ConfigureImageButton(closeButton, 1394, 806, 150, 63, ConfigureButton(closeButton, 1036, 646, 150, 63,
path, CloseButton_Click); resizedNormalStateImageForSceneSoundEffects, resizedNormalStateImageForSceneSoundEffects, resizedNormalStateImageForSceneSoundEffects,
CloseButton_Click);
this.Controls.Add(closeButton); this.Controls.Add(closeButton);
} }
@ -121,7 +96,7 @@ namespace DualScreenDemo
if (!pictureBoxSceneSoundEffects.Visible) if (!pictureBoxSceneSoundEffects.Visible)
{ {
ShowImageOnPictureBoxSceneSoundEffects(Path.Combine(Application.StartupPath, @"themes\superstar\場景音效\場景音效.png")); ShowImageOnPictureBoxSceneSoundEffects(Path.Combine(Application.StartupPath, @"themes\superstar\555022.jpg"));
SetPictureBoxSceneSoundEffectsAndButtonsVisibility(true); SetPictureBoxSceneSoundEffectsAndButtonsVisibility(true);
} }
else else
@ -156,31 +131,29 @@ namespace DualScreenDemo
public void PlayApplauseSound() public void PlayApplauseSound()
{ {
mediaPlayer.URL = Path.Combine(Application.StartupPath,"sounds" ,"zs.m4a"); mediaPlayer.URL = Path.Combine(Application.StartupPath, "sounds", "zs.m4a");
mediaPlayer.controls.play(); mediaPlayer.controls.play();
} }
// 按鈕位置需要更改,底圖需要更改
private void ShowImageOnPictureBoxSceneSoundEffects(string imagePath) private void ShowImageOnPictureBoxSceneSoundEffects(string imagePath)
{ {
if (File.Exists(imagePath)) Bitmap originalImage = new Bitmap(imagePath);
{
// 直接載入完整圖
Bitmap image = new Bitmap(imagePath);
// 顯示在 PictureBox 上
pictureBoxSceneSoundEffects.Image = image;
// 設定 PictureBox 的大小與位置(依你的需要調整) Rectangle cropArea = new Rectangle(859, 427, 342, 295);
ResizeAndPositionPictureBox(pictureBoxSceneSoundEffects, 850, 450, image.Width , image.Height);
Bitmap croppedImage = CropImage(originalImage, cropArea);
pictureBoxSceneSoundEffects.Image = croppedImage;
ResizeAndPositionPictureBox(pictureBoxSceneSoundEffects, cropArea.X, cropArea.Y, cropArea.Width, cropArea.Height);
pictureBoxSceneSoundEffects.Visible = true; pictureBoxSceneSoundEffects.Visible = true;
} }
else
{
Console.WriteLine("圖片檔案不存在:" + imagePath);
}
}
private void TogglePictureBoxSceneSoundEffectsButtonsVisibility() private void TogglePictureBoxSceneSoundEffectsButtonsVisibility()
{ {

View File

@ -116,19 +116,18 @@ namespace DualScreenDemo
} }
private async void VodButton_Click(object sender, EventArgs e) private void VodButton_Click(object sender, EventArgs e)
{ {
SetVodScreenPictureBoxAndButtonsVisibility(false);
await Task.Delay(1000);
OverlayForm.MainForm.AddSongToPlaylist(currentSelectedSong);
OverlayForm.MainForm.AddSongToPlaylist(currentSelectedSong);
SetVodScreenPictureBoxAndButtonsVisibility(false);
} }
private void InsertButton_Click(object sender, EventArgs e) private void InsertButton_Click(object sender, EventArgs e)
{ {
SetVodScreenPictureBoxAndButtonsVisibility(false);
OverlayForm.MainForm.InsertSongToPlaylist(currentSelectedSong);
OverlayForm.MainForm.InsertSongToPlaylist(currentSelectedSong);
SetVodScreenPictureBoxAndButtonsVisibility(false);
} }
private void AlbumButton_Click(object sender, EventArgs e) private void AlbumButton_Click(object sender, EventArgs e)

View File

@ -242,23 +242,6 @@ namespace DualScreenDemo
LoadConnectionStringFromFile("test.env"); LoadConnectionStringFromFile("test.env");
} }
public bool IsAppResponsive()
{
try
{
var form = this;
if (form != null)
{
bool dummy = form.InvokeRequired; // 如果 Invoke 卡死,會丟錯
return true;
}
}
catch
{
return false;
}
return true;
}
// 添加 DPI 感知支持 // 添加 DPI 感知支持
[DllImport("user32.dll")] [DllImport("user32.dll")]
@ -957,12 +940,12 @@ namespace DualScreenDemo
} }
catch (Exception ex) catch (Exception ex)
{ {
Console.WriteLine("Failed to send command: " + ex.Message); MessageBox.Show("Failed to send command: " + ex.Message);
} }
} }
else else
{ {
Console.WriteLine("Serial port is not open."); MessageBox.Show("Serial port is not open.");
} }
} }
@ -1577,7 +1560,7 @@ namespace DualScreenDemo
return; return;
} }
// 2. 確認本地文件夾是否存在(不存在則創立) // 2. 比較本地和服務器文件夾
if (!Directory.Exists(localVideoPath)) if (!Directory.Exists(localVideoPath))
{ {
Directory.CreateDirectory(localVideoPath); Directory.CreateDirectory(localVideoPath);
@ -1592,7 +1575,7 @@ namespace DualScreenDemo
.Select(f => new FileInfo(f)) .Select(f => new FileInfo(f))
.ToDictionary(f => f.Name, f => f); .ToDictionary(f => f.Name, f => f);
// 3-1. 檢查並更新文件 // 3. 檢查並更新文件
foreach (var serverFile in serverFiles) foreach (var serverFile in serverFiles)
{ {
bool needsCopy = false; bool needsCopy = false;
@ -1624,22 +1607,7 @@ namespace DualScreenDemo
} }
} }
} }
// 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}");
}
}
}
// 4. 載入更新後的本地文件 // 4. 載入更新後的本地文件
LoadLocalVideoFiles(); LoadLocalVideoFiles();
} }
@ -2093,14 +2061,14 @@ namespace DualScreenDemo
videoPlayerForm.ReplayCurrentSong(); videoPlayerForm.ReplayCurrentSong();
} }
public void PauseButton_Click(object sender, EventArgs e) private void PauseButton_Click(object sender, EventArgs e)
{ {
videoPlayerForm.Pause(); videoPlayerForm.Pause();
pauseButton.Visible = false; pauseButton.Visible = false;
playButton.Visible = true; playButton.Visible = true;
} }
public void PlayButton_Click(object sender, EventArgs e) private void PlayButton_Click(object sender, EventArgs e)
{ {
videoPlayerForm.Play(); videoPlayerForm.Play();
playButton.Visible = false; playButton.Visible = false;

View File

@ -2,7 +2,7 @@ using System.IO;
using Microsoft.Win32; using Microsoft.Win32;
using System.Diagnostics; using System.Diagnostics;
using DBObj; using DBObj;
using DataCheck; using HeartbeatSender;
namespace DualScreenDemo namespace DualScreenDemo
{ {
@ -17,27 +17,15 @@ namespace DualScreenDemo
[STAThread] [STAThread]
static void Main() static void Main()
{ {
Console.WriteLine("隱藏滑鼠游標"); Console.WriteLine("隱藏滑鼠游標");
Cursor.Hide(); // Cursor.Hide();
AppDomain.CurrentDomain.ProcessExit += (s, e) => AppDomain.CurrentDomain.ProcessExit += (s, e) =>
{ {
Cursor.Show(); 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("正在與中控取得聯繫..."); Console.WriteLine("正在與中控取得聯繫");
var sender = new HeartbeatSender.heartbeatSender(); /*var sender = new HeartbeatSender.heartbeatSender();
// 同步呼叫非同步登入取得 token // 同步呼叫非同步登入取得 token
bool loginSuccess = sender.LoginAndGetTokenAsync().GetAwaiter().GetResult(); bool loginSuccess = sender.LoginAndGetTokenAsync().GetAwaiter().GetResult();
@ -47,26 +35,21 @@ namespace DualScreenDemo
// 先送一次心跳 (同步呼叫) // 先送一次心跳 (同步呼叫)
sender.SendHeartbeatAsync().GetAwaiter().GetResult(); sender.SendHeartbeatAsync().GetAwaiter().GetResult();
// 背景持續每5分鐘送心跳 // 背景持續每3秒送心跳
_ = Task.Run(async () => _ = Task.Run(async () =>
{ {
while (true) while (true)
{ {
await sender.SendHeartbeatAsync(); await sender.SendHeartbeatAsync();
await Task.Delay(300000); // 每5min送一次 await Task.Delay(300000); // 每3秒送一次
} }
}); });
Console.WriteLine("正在發送心跳中...");
} }
else else
{ {
Console.WriteLine("登入失敗,無法送出心跳"); Console.WriteLine("登入失敗,無法送出心跳");
} }
//之後需要做添加同步菜單+酒單+背景圖(若圖規格有做正規化) */
// DataCheck.cs 有預寫好相關流程函式
Console.WriteLine("更新菜單和酒單...");
dataCheck Checkprocess = new dataCheck();
try try
{ {
// COM 初始化 // COM 初始化
@ -123,22 +106,9 @@ namespace DualScreenDemo
primaryForm.ShowSendOffScreen(); primaryForm.ShowSendOffScreen();
}; };
} }
WatchDog _watchDog = new WatchDog(
() => VideoPlayerForm.Instance.GetCurrentVideoStatus(),
() => primaryForm.IsAppResponsive()
);
_watchDog.Start();
Console.WriteLine("啟動WatchDog進行監聽");
AppDomain.CurrentDomain.ProcessExit += (s, e) =>
{
_watchDog.Stop();
};
primaryForm.Show(); primaryForm.Show();
Application.Run(primaryForm); Application.Run(primaryForm);
} }
catch (Exception ex) catch (Exception ex)
{ {

View File

@ -1,6 +1,5 @@
using System.IO.Ports; using System.IO.Ports;
using System.Text; using System.Text;
using System.Collections.Concurrent;
namespace DualScreenDemo namespace DualScreenDemo
{ {
@ -49,10 +48,6 @@ namespace DualScreenDemo
// 綁定資料接收事件 // 綁定資料接收事件
mySerialPort.DataReceived += new SerialDataReceivedEventHandler(DataReceivedHandler); mySerialPort.DataReceived += new SerialDataReceivedEventHandler(DataReceivedHandler);
AppDomain.CurrentDomain.ProcessExit += (s, e) =>
{
cts.Cancel();
};
try try
{ {
@ -66,10 +61,6 @@ namespace DualScreenDemo
} }
private readonly ConcurrentQueue<string> commandQueue = new();
public readonly CancellationTokenSource cts = new();
private bool isProcessing = false;
private void DataReceivedHandler(object sender, SerialDataReceivedEventArgs e) private void DataReceivedHandler(object sender, SerialDataReceivedEventArgs e)
{ {
try try
@ -77,14 +68,19 @@ namespace DualScreenDemo
SerialPort sp = (SerialPort)sender; SerialPort sp = (SerialPort)sender;
if (!sp.IsOpen) if (!sp.IsOpen)
{
// Console.WriteLine("串列埠未開啟,無法接收資料。");
return; return;
}
int bytesToRead = sp.BytesToRead; int bytesToRead = sp.BytesToRead;
if (bytesToRead > 0) if (bytesToRead > 0)
{ {
byte[] buffer = new byte[bytesToRead]; byte[] buffer = new byte[bytesToRead];
int bytesRead = sp.Read(buffer, 0, bytesToRead); int bytesRead = sp.Read(buffer, 0, bytesToRead);
StringBuilder hexData = new StringBuilder(bytesRead * 2); StringBuilder hexData = new StringBuilder(bytesRead * 2);
for (int i = 0; i < bytesRead; i++) for (int i = 0; i < bytesRead; i++)
{ {
@ -92,18 +88,24 @@ namespace DualScreenDemo
} }
string indata = hexData.ToString(); string indata = hexData.ToString();
// Console.WriteLine($"接收到的資料 (Hex): {indata}");
Console.WriteLine($"[{DateTime.Now:HH:mm:ss.fff}] 遙控器: {indata}");
// 將資料放入佇列 Task.Run(() =>
commandQueue.Enqueue(indata);
// 如果尚未啟動處理迴圈,則啟動
if (!isProcessing)
{ {
isProcessing = true; try
Task.Run(ProcessQueueAsync); {
commandHandler.ProcessData(indata);
} }
catch (Exception processEx)
{
Console.WriteLine($"處理資料時發生錯誤: {processEx.Message}");
}
});
}
else
{
// Console.WriteLine("未接收到任何資料。");
} }
} }
catch (Exception ex) catch (Exception ex)
@ -111,27 +113,6 @@ namespace DualScreenDemo
Console.WriteLine($"接收資料時發生錯誤: {ex.Message}"); Console.WriteLine($"接收資料時發生錯誤: {ex.Message}");
} }
} }
private async Task ProcessQueueAsync()
{
while (!cts.Token.IsCancellationRequested)
{
while (commandQueue.TryDequeue(out var cmd))
{
try
{
commandHandler.ProcessData(cmd);
}
catch (Exception processEx)
{
Console.WriteLine($"處理資料時發生錯誤: {processEx.Message}");
}
}
await Task.Delay(10); // 避免 CPU 空轉
}
cts.Cancel();
isProcessing = false;
}

View File

@ -3,8 +3,6 @@ using System.Runtime.InteropServices;
using DirectShowLib; using DirectShowLib;
using DBObj; using DBObj;
using OverlayFormObj; using OverlayFormObj;
using DualScreenDemo.Shared;
namespace DualScreenDemo namespace DualScreenDemo
{ {
public class VideoPlayerForm : Form public class VideoPlayerForm : Form
@ -458,12 +456,10 @@ namespace DualScreenDemo
int newHeight = (int)(designHeight * scale); int newHeight = (int)(designHeight * scale);
// trash location with trash flexible of screen size. // trash location with trash flexible of screen size.
if (actualWidth == 1024) if (actualWidth == 1024){
{
newWidth = (int)(newWidth * 0.84f); newWidth = (int)(newWidth * 0.84f);
} }
else if (actualWidth == 1440) else if (actualWidth == 1440){
{
newWidth = (int)(newWidth * 0.9f); newWidth = (int)(newWidth * 0.9f);
} }
videoWindowPrimary = (IVideoWindow)videoRendererPrimary; videoWindowPrimary = (IVideoWindow)videoRendererPrimary;
@ -613,7 +609,7 @@ namespace DualScreenDemo
if (File.Exists(welcomePath)) if (File.Exists(welcomePath))
{ {
publicPlaylist.Add(new SongData( publicPlaylist.Add(new SongData(
"0", "歡迎光臨", "", "", "", welcomePath, "0", "歡迎光臨", "", "","", welcomePath,
"", "", "", "", 1 "", "", "", "", 1
)); ));
} }
@ -626,7 +622,7 @@ namespace DualScreenDemo
{ {
publicPlaylist.Add(new SongData( publicPlaylist.Add(new SongData(
i.ToString(), $"背景音樂{i:D2}", i.ToString(), $"背景音樂{i:D2}",
"", "", "", bgmPath, "", "", "", "", 1 "", "", "",bgmPath, "", "", "", "", 1
)); ));
} }
} }
@ -682,8 +678,7 @@ namespace DualScreenDemo
if (overlayForm.InvokeRequired) if (overlayForm.InvokeRequired)
{ {
overlayForm.Invoke(new MethodInvoker(() => overlayForm.Invoke(new MethodInvoker(() => {
{
overlayForm.UpdateMarqueeText(nextSongText, OverlayForm.MarqueeStartPosition.Middle, Color.White); overlayForm.UpdateMarqueeText(nextSongText, OverlayForm.MarqueeStartPosition.Middle, Color.White);
})); }));
} }
@ -696,8 +691,7 @@ namespace DualScreenDemo
// 重置跑马灯文本 // 重置跑马灯文本
if (overlayForm.InvokeRequired) if (overlayForm.InvokeRequired)
{ {
overlayForm.Invoke(new MethodInvoker(() => overlayForm.Invoke(new MethodInvoker(() => {
{
overlayForm.ResetMarqueeTextToWelcomeMessage(); overlayForm.ResetMarqueeTextToWelcomeMessage();
})); }));
} }
@ -937,9 +931,8 @@ namespace DualScreenDemo
/*如果當前為公播,不可以+1*/ /*如果當前為公播,不可以+1*/
bool isPlayingPublicList = PrimaryForm.userRequestedSongs.Count == 0 || bool isPlayingPublicList = PrimaryForm.userRequestedSongs.Count == 0 ||
(PrimaryForm.currentSongIndexInHistory >= PrimaryForm.userRequestedSongs.Count - 1 && PrimaryForm.Instance.videoPlayerForm.IsPlayingPublicSong); (PrimaryForm.currentSongIndexInHistory >= PrimaryForm.userRequestedSongs.Count - 1 && PrimaryForm.Instance.videoPlayerForm.IsPlayingPublicSong);
if (!isPlayingPublicList) if(!isPlayingPublicList){
{ PrimaryForm.currentSongIndexInHistory+=1;
PrimaryForm.currentSongIndexInHistory += 1;
} }
Console.WriteLine("currentSongIndexInHistory : " + PrimaryForm.currentSongIndexInHistory); Console.WriteLine("currentSongIndexInHistory : " + PrimaryForm.currentSongIndexInHistory);
} }
@ -1609,7 +1602,7 @@ namespace DualScreenDemo
} }
catch (Exception ex) catch (Exception ex)
{ {
Console.WriteLine(ex.Message); Console.WriteLine( ex.Message);
} }
} }
@ -1656,7 +1649,7 @@ namespace DualScreenDemo
} }
catch (Exception ex) catch (Exception ex)
{ {
Console.WriteLine(ex.Message); Console.WriteLine( ex.Message);
} }
} }
@ -1824,67 +1817,5 @@ namespace DualScreenDemo
Console.WriteLine("Error occurred: " + ex.Message); Console.WriteLine("Error occurred: " + ex.Message);
} }
} }
public VideoStatus GetCurrentVideoStatus()
{
var status = new VideoStatus();
try
{
IMediaSeeking mediaSeekingSecondary = graphBuilderSecondary as IMediaSeeking;
if (mediaSeekingSecondary != null)
{
long position;
if (mediaSeekingSecondary.GetCurrentPosition(out position) >= 0)
{
status.PositionSeconds = position / 10000000.0;
}
else
{
status.LastError = "無法取得影片播放位置";
status.PositionSeconds = -1;
}
}
else
{
status.LastError = "mediaSeekingSecondary 物件為 null";
status.PositionSeconds = -1;
}
if (mediaControlSecondary != null)
{
FilterState stateCode;
int hr = mediaControlSecondary.GetState(100, out stateCode);
if (hr >= 0)
{
var state = (FilterState)stateCode;
status.PlayState = state.ToString();
status.IsGraphOk = true;
}
else
{
status.PlayState = "無法取得播放狀態";
status.IsGraphOk = false;
}
}
else
{
status.PlayState = "mediaControlSecondary 物件為 null";
status.IsGraphOk = false;
}
}
catch (Exception ex)
{
status.LastError = "取得影片狀態時發生例外:" + ex.Message;
status.PositionSeconds = -1;
status.PlayState = "Error";
status.IsGraphOk = false;
}
return status;
}
} }
} }

View File

@ -1,84 +0,0 @@
using System;
using System.IO;
using System.Threading;
using System.Threading.Tasks;
using System.Windows.Forms;
using DualScreenDemo.Shared;
public class WatchDog
{
private Func<VideoStatus> getVideoStatus;
private Func<bool> isApplicationResponsive;
private Thread watchdogThread;
private bool running = false;
private double lastPosition = -1;
private int freezeCounter = 0;
public WatchDog(Func<VideoStatus> getVideoPositionFunc, Func<bool> isAppResponsiveFunc)
{
getVideoStatus = getVideoPositionFunc;
isApplicationResponsive = isAppResponsiveFunc;
}
public void Start()
{
running = true;
watchdogThread = new Thread(Run);
watchdogThread.IsBackground = true;
watchdogThread.Start();
}
public void Stop()
{
running = false;
watchdogThread?.Join();
}
private void Run()
{
while (running)
{
var status = getVideoStatus(); // 改用 getVideoStatus 取得完整狀態
bool responsive = isApplicationResponsive();
if (!status.IsGraphOk)
{
Log($"影片圖表異常: {status.LastError}");
}
else if(status.PlayState != "Paused")
{
double currentPosition = status.PositionSeconds;
if (Math.Abs(currentPosition - lastPosition) < 0.001)
{
freezeCounter++;
if (freezeCounter >= 3)
{
Log($"影片疑似卡死3次位置沒變位置={currentPosition:F2}秒,播放狀態={status.PlayState}");
freezeCounter = 0; // 記得 reset
}
}
else
{
freezeCounter = 0;
}
lastPosition = currentPosition;
}
if (!responsive)
{
Log("UI 疑似卡死Invoke 失敗)");
}
Thread.Sleep(5000);
}
}
private void Log(string message)
{
string logFilePath = Path.Combine("txt", "watchdog_log.txt");
File.AppendAllText(logFilePath, $"{DateTime.Now}: {message}{Environment.NewLine}");
}
}

View File

@ -350,15 +350,15 @@
const row = document.createElement('tr'); const row = document.createElement('tr');
const songNameCell = document.createElement('td'); const songNameCell = document.createElement('td');
const singerCell = document.createElement('td'); const singerCell = document.createElement('td');
// const languageCell = document.createElement('td'); const languageCell = document.createElement('td');
songNameCell.textContent = song.Song; songNameCell.textContent = song.Song;
singerCell.textContent = song.ArtistA; singerCell.textContent = song.ArtistA;
// languageCell.textContent = song.Category; languageCell.textContent = song.Category;
row.appendChild(songNameCell); row.appendChild(songNameCell);
row.appendChild(singerCell); row.appendChild(singerCell);
// row.appendChild(languageCell); row.appendChild(languageCell);
row.addEventListener('click', (e) => { row.addEventListener('click', (e) => {
e.preventDefault(); e.preventDefault();
@ -369,7 +369,7 @@
document.getElementById('detail-song-name').textContent = song.Song; document.getElementById('detail-song-name').textContent = song.Song;
document.getElementById('detail-song-number').textContent = song.SongNumber; document.getElementById('detail-song-number').textContent = song.SongNumber;
document.getElementById('detail-singer').textContent = song.ArtistA; document.getElementById('detail-singer').textContent = song.ArtistA;
// document.getElementById('detail-language').textContent = song.Category; document.getElementById('detail-language').textContent = song.Category;
document.getElementById('song-details').style.display = 'block'; document.getElementById('song-details').style.display = 'block';
}); });

View File

@ -80,20 +80,26 @@
} }
.content { .content {
display: grid; display: grid;
grid-template-columns: repeat(auto-fill, minmax(100px, 1fr)); grid-template-columns: repeat(3, 1fr); /* 固定3欄 */
gap: 5px; gap: 5px; /* 移除格子之間的空隙 */
margin: 0; /* 沒有外邊距 */
padding: 0; /* 沒有內邊距 */
width: 36.5vw; /* 滿版寬度 */
} }
@media (max-width: 600px) { @media (max-width: 600px) {
.content { .content {
display: grid; display: grid;
grid-template-columns: repeat(3, 1fr); grid-template-columns: repeat(3, 1fr); /* 固定3欄 */
gap: 5px; gap: 5px; /* 移除格子之間的空隙 */
margin: 0; /* 沒有外邊距 */
padding: 0; /* 沒有內邊距 */
width: 78vw; /* 滿版寬度 */
} }
} }
.card { .card {
background: linear-gradient(135deg, #FF4081, #FF4081); background: linear-gradient(135deg, #FF4081, #FF4081);
color: white; color: white;
width: 100%; width: 130px;
display: flex; display: flex;
align-items: center; align-items: center;
justify-content: center; justify-content: center;