Compare commits
11 Commits
3d255da675
...
107639f4c9
Author | SHA1 | Date | |
---|---|---|---|
107639f4c9 | |||
dd2a0f3d7b | |||
5db51dda99 | |||
7eb8f395a0 | |||
eafd322a0d | |||
f07129c18f | |||
5d2ea02263 | |||
4a76e14102 | |||
4d09ed914a | |||
39c2a39fd8 | |||
f2d633e688 |
@ -26,10 +26,9 @@ namespace DualScreenDemo
|
|||||||
/// 遙控器接收資料
|
/// 遙控器接收資料
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public async Task ProcessData(string indata)
|
public async Task ProcessData(string indata)
|
||||||
{
|
{ AddToHistory(indata);
|
||||||
AddToHistory(indata);
|
|
||||||
// 遙控器測試
|
// 遙控器測試
|
||||||
Console.WriteLine("遙控器: " + indata);
|
// Console.WriteLine($"[{DateTime.Now:HH:mm:ss.fff}] 遙控器: {indata}");
|
||||||
switch (indata)
|
switch (indata)
|
||||||
{
|
{
|
||||||
case "A261A4": // 輸入
|
case "A261A4": // 輸入
|
||||||
@ -70,12 +69,12 @@ namespace DualScreenDemo
|
|||||||
// 原唱
|
// 原唱
|
||||||
case "A26CA4":
|
case "A26CA4":
|
||||||
Console.WriteLine("ToggleVocalRemoval Invoked");
|
Console.WriteLine("ToggleVocalRemoval Invoked");
|
||||||
InvokeAction(() => VideoPlayerForm.Instance.ToggleVocalRemoval());
|
SafeInvokeAction("A26CA4",() => VideoPlayerForm.Instance.ToggleVocalRemoval());
|
||||||
InvokeAction(() => OverlayForm.MainForm.ShowOriginalSongLabel());
|
SafeInvokeAction("A26CA4",() => OverlayForm.MainForm.ShowOriginalSongLabel());
|
||||||
break;
|
break;
|
||||||
// 導唱
|
// 導唱
|
||||||
case "A26EA4":
|
case "A26EA4":
|
||||||
InvokeAction(() => VideoPlayerForm.Instance.ToggleVocalRemoval());
|
SafeInvokeAction("A26EA4",() => VideoPlayerForm.Instance.ToggleVocalRemoval());
|
||||||
break;
|
break;
|
||||||
case "A26DA4":
|
case "A26DA4":
|
||||||
PauseOrResumeSong();
|
PauseOrResumeSong();
|
||||||
@ -88,106 +87,131 @@ namespace DualScreenDemo
|
|||||||
HandleArtistAnnouncements();
|
HandleArtistAnnouncements();
|
||||||
break;
|
break;
|
||||||
case "A2B3A4":
|
case "A2B3A4":
|
||||||
InvokeAction(() => OverlayForm.MainForm.HideAllLabels());
|
SafeInvokeAction("A2B3A4",() => OverlayForm.MainForm.HideAllLabels());
|
||||||
InvokeAction(() => OverlayForm.MainForm.ShowVolumeUpLabel());
|
SafeInvokeAction("A2B3A4",() => OverlayForm.MainForm.ShowVolumeUpLabel());
|
||||||
break;
|
break;
|
||||||
case "A2B4A4":
|
case "A2B4A4":
|
||||||
InvokeAction(() => OverlayForm.MainForm.HideAllLabels());
|
SafeInvokeAction("A2B4A4",() => OverlayForm.MainForm.HideAllLabels());
|
||||||
InvokeAction(() => OverlayForm.MainForm.ShowVolumeDownLabel());
|
SafeInvokeAction("A2B4A4",() => OverlayForm.MainForm.ShowVolumeDownLabel());
|
||||||
break;
|
break;
|
||||||
case "A2B5A4":
|
case "A2B5A4":
|
||||||
InvokeAction(() => OverlayForm.MainForm.HideAllLabels());
|
SafeInvokeAction("A2B5A4",() => OverlayForm.MainForm.HideAllLabels());
|
||||||
InvokeAction(() => OverlayForm.MainForm.ShowMicUpLabel());
|
SafeInvokeAction("A2B5A4",() => OverlayForm.MainForm.ShowMicUpLabel());
|
||||||
break;
|
break;
|
||||||
case "A2B6A4":
|
case "A2B6A4":
|
||||||
InvokeAction(() => OverlayForm.MainForm.HideAllLabels());
|
SafeInvokeAction("A2B6A4",() => OverlayForm.MainForm.HideAllLabels());
|
||||||
InvokeAction(() => OverlayForm.MainForm.ShowMicDownLabel());
|
SafeInvokeAction("A2B6A4",() => OverlayForm.MainForm.ShowMicDownLabel());
|
||||||
break;
|
break;
|
||||||
case "A2C2A4":
|
case "A2C2A4":
|
||||||
InvokeAction(() => OverlayForm.MainForm.HidemicLabels());
|
SafeInvokeAction("A2C2A4",() => OverlayForm.MainForm.HidemicLabels());
|
||||||
InvokeAction(() => OverlayForm.MainForm.ShowStandardLabel());
|
SafeInvokeAction("A2C2A4",() => OverlayForm.MainForm.ShowStandardLabel());
|
||||||
break;
|
break;
|
||||||
case "A2C3A4":
|
case "A2C3A4":
|
||||||
InvokeAction(() => OverlayForm.MainForm.HidemicLabels());
|
SafeInvokeAction("A2C3A4",() => OverlayForm.MainForm.HidemicLabels());
|
||||||
InvokeAction(() => OverlayForm.MainForm.ShowProfessionalLabel());
|
SafeInvokeAction("A2C3A4",() => OverlayForm.MainForm.ShowProfessionalLabel());
|
||||||
break;
|
break;
|
||||||
case "A2C4A4":
|
case "A2C4A4":
|
||||||
InvokeAction(() => OverlayForm.MainForm.HidemicLabels());
|
SafeInvokeAction("A2C4A4",() => OverlayForm.MainForm.HidemicLabels());
|
||||||
InvokeAction(() => OverlayForm.MainForm.ShowSquareLabel());
|
SafeInvokeAction("A2C4A4",() => OverlayForm.MainForm.ShowSquareLabel());
|
||||||
break;
|
break;
|
||||||
case "A2C1A4":
|
case "A2C1A4":
|
||||||
InvokeAction(() => OverlayForm.MainForm.HidemicLabels());
|
SafeInvokeAction("A2C1A4",() => OverlayForm.MainForm.HidemicLabels());
|
||||||
InvokeAction(() => OverlayForm.MainForm.ShowSingDownLabel());
|
SafeInvokeAction("A2C1A4",() => OverlayForm.MainForm.ShowSingDownLabel());
|
||||||
break;
|
break;
|
||||||
case "A2D5A4":
|
case "A2D5A4":
|
||||||
InvokeAction(() => OverlayForm.MainForm.HideAllLabels());
|
SafeInvokeAction("A2D5A4",() => OverlayForm.MainForm.HideAllLabels());
|
||||||
InvokeAction(() => OverlayForm.MainForm.ShowBrightLabel());
|
SafeInvokeAction("A2D5A4",() => OverlayForm.MainForm.ShowBrightLabel());
|
||||||
break;
|
break;
|
||||||
case "A2D7A4":
|
case "A2D7A4":
|
||||||
InvokeAction(() => OverlayForm.MainForm.HideAllLabels());
|
SafeInvokeAction("A2D7A4",() => OverlayForm.MainForm.HideAllLabels());
|
||||||
InvokeAction(() => OverlayForm.MainForm.ShowRomanticLabel());
|
SafeInvokeAction("A2D7A4",() => OverlayForm.MainForm.ShowRomanticLabel());
|
||||||
break;
|
break;
|
||||||
/* case "A27CA4":
|
/* case "A27CA4":
|
||||||
InvokeAction(() => OverlayForm.MainForm.ShowMaleKeyLabel());
|
InvokeAction(() => OverlayForm.MainForm.ShowMaleKeyLabel());
|
||||||
break;
|
break;
|
||||||
case "A282A4":
|
case "A282A4":
|
||||||
InvokeAction(() => OverlayForm.MainForm.ShowFemaleKeyLabel());
|
InvokeAction(() => OverlayForm.MainForm.ShowFemaleKeyLabel());
|
||||||
break;*/
|
break;*/
|
||||||
case "A2D6A4":
|
case "A2D6A4":
|
||||||
InvokeAction(() => OverlayForm.MainForm.HideAllLabels());
|
SafeInvokeAction("A2D6A4",() => OverlayForm.MainForm.HideAllLabels());
|
||||||
InvokeAction(() => OverlayForm.MainForm.ShowSoftLabel());
|
SafeInvokeAction("A2D6A4",() => OverlayForm.MainForm.ShowSoftLabel());
|
||||||
break;
|
break;
|
||||||
case "A2D8A4":
|
case "A2D8A4":
|
||||||
InvokeAction(() => OverlayForm.MainForm.HideAllLabels());
|
SafeInvokeAction("A2D8A4",() => OverlayForm.MainForm.HideAllLabels());
|
||||||
InvokeAction(() => OverlayForm.MainForm.ShowDynamicLabel());
|
SafeInvokeAction("A2D8A4",() => OverlayForm.MainForm.ShowDynamicLabel());
|
||||||
break;
|
break;
|
||||||
case "A275A4":
|
case "A275A4":
|
||||||
InvokeAction(() => OverlayForm.MainForm.HideAllLabels());
|
SafeInvokeAction("A275A4",() => OverlayForm.MainForm.HideAllLabels());
|
||||||
InvokeAction(() => OverlayForm.MainForm.ShowTintLabel());
|
SafeInvokeAction("A275A4",() => OverlayForm.MainForm.ShowTintLabel());
|
||||||
break;
|
break;
|
||||||
case "A283A4":
|
case "A283A4":
|
||||||
InvokeAction(() => OverlayForm.MainForm.HideAllLabels());
|
SafeInvokeAction("A283A4",() => OverlayForm.MainForm.HideAllLabels());
|
||||||
InvokeAction(() => OverlayForm.MainForm.ShowKeyUpLabel("↑升4調"));
|
SafeInvokeAction("A283A4",() => OverlayForm.MainForm.ShowKeyUpLabel("↑升4調"));
|
||||||
break;
|
break;
|
||||||
case "A282A4":
|
case "A282A4":
|
||||||
InvokeAction(() => OverlayForm.MainForm.HideAllLabels());
|
SafeInvokeAction("A282A4",() => OverlayForm.MainForm.HideAllLabels());
|
||||||
InvokeAction(() => OverlayForm.MainForm.ShowKeyUpLabel("↑升3調"));
|
SafeInvokeAction("A282A4",() => OverlayForm.MainForm.ShowKeyUpLabel("↑升3調"));
|
||||||
break;
|
break;
|
||||||
case "A281A4":
|
case "A281A4":
|
||||||
InvokeAction(() => OverlayForm.MainForm.HideAllLabels());
|
SafeInvokeAction("A281A4",() => OverlayForm.MainForm.HideAllLabels());
|
||||||
InvokeAction(() => OverlayForm.MainForm.ShowKeyUpLabel("↑升2調"));
|
SafeInvokeAction("A281A4",() => OverlayForm.MainForm.ShowKeyUpLabel("↑升2調"));
|
||||||
break;
|
break;
|
||||||
case "A280A4":
|
case "A280A4":
|
||||||
InvokeAction(() => OverlayForm.MainForm.HideAllLabels());
|
SafeInvokeAction("A280A4",() => OverlayForm.MainForm.HideAllLabels());
|
||||||
InvokeAction(() => OverlayForm.MainForm.ShowKeyUpLabel("↑升1調"));
|
SafeInvokeAction("A280A4",() => OverlayForm.MainForm.ShowKeyUpLabel("↑升1調"));
|
||||||
break;
|
break;
|
||||||
case "A27FA4":
|
case "A27FA4":
|
||||||
InvokeAction(() => OverlayForm.MainForm.HideAllLabels());
|
SafeInvokeAction("A27FA4",() => OverlayForm.MainForm.HideAllLabels());
|
||||||
InvokeAction(() => OverlayForm.MainForm.ShowStandardKeyLabel());
|
SafeInvokeAction("A27FA4",() => OverlayForm.MainForm.ShowStandardKeyLabel());
|
||||||
break;
|
break;
|
||||||
case "A27EA4":
|
case "A27EA4":
|
||||||
InvokeAction(() => OverlayForm.MainForm.HideAllLabels());
|
SafeInvokeAction("A27EA4",() => OverlayForm.MainForm.HideAllLabels());
|
||||||
InvokeAction(() => OverlayForm.MainForm.ShowKeyDownLabel("↓降1調"));
|
SafeInvokeAction("A27EA4",() => OverlayForm.MainForm.ShowKeyDownLabel("↓降1調"));
|
||||||
break;
|
break;
|
||||||
case "A27DA4":
|
case "A27DA4":
|
||||||
InvokeAction(() => OverlayForm.MainForm.HideAllLabels());
|
SafeInvokeAction("A27EA4",() => OverlayForm.MainForm.HideAllLabels());
|
||||||
InvokeAction(() => OverlayForm.MainForm.ShowKeyDownLabel("↓降2調"));
|
SafeInvokeAction("A27EA4",() => OverlayForm.MainForm.ShowKeyDownLabel("↓降2調"));
|
||||||
break;
|
break;
|
||||||
case "A27CA4":
|
case "A27CA4":
|
||||||
InvokeAction(() => OverlayForm.MainForm.HideAllLabels());
|
SafeInvokeAction("A27CA4",() => OverlayForm.MainForm.HideAllLabels());
|
||||||
InvokeAction(() => OverlayForm.MainForm.ShowKeyDownLabel("↓降3調"));
|
SafeInvokeAction("A27CA4",() => OverlayForm.MainForm.ShowKeyDownLabel("↓降3調"));
|
||||||
break;
|
break;
|
||||||
case "A27BA4":
|
case "A27BA4":
|
||||||
InvokeAction(() => OverlayForm.MainForm.HideAllLabels());
|
SafeInvokeAction("A27BA4",() => OverlayForm.MainForm.HideAllLabels());
|
||||||
InvokeAction(() => OverlayForm.MainForm.ShowKeyDownLabel("↓降4調"));
|
SafeInvokeAction("A27BA4",() => OverlayForm.MainForm.ShowKeyDownLabel("↓降4調"));
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
if (Regex.IsMatch(indata, @"^A23\d+A4$"))
|
if (Regex.IsMatch(indata, @"^A23\d+A4$"))
|
||||||
{
|
{
|
||||||
HandleNumberInput(indata);
|
HandleNumberInput(indata);
|
||||||
}
|
}
|
||||||
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)
|
||||||
|
140
DataCheck.cs
Normal file
140
DataCheck.cs
Normal file
@ -0,0 +1,140 @@
|
|||||||
|
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}");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
10
DualScreenDemo.Shared.cs
Normal file
10
DualScreenDemo.Shared.cs
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
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; }
|
||||||
|
}
|
||||||
|
}
|
@ -168,7 +168,10 @@ namespace HeartbeatSender
|
|||||||
private float GetTotalMemoryInMB()
|
private float GetTotalMemoryInMB()
|
||||||
{
|
{
|
||||||
var computerInfo = new ComputerInfo();
|
var computerInfo = new ComputerInfo();
|
||||||
return computerInfo.TotalPhysicalMemory / (1024f * 1024f); // 轉 MB
|
var totalMB = computerInfo.TotalPhysicalMemory / (1024f);
|
||||||
|
var availableMB = computerInfo.AvailablePhysicalMemory / (1024f);
|
||||||
|
var usedMB = totalMB - availableMB;
|
||||||
|
return usedMB; // 轉 MB
|
||||||
}
|
}
|
||||||
private float GetDiskTotalSizeInGB(string driveLetter = "C")
|
private float GetDiskTotalSizeInGB(string driveLetter = "C")
|
||||||
{
|
{
|
||||||
|
@ -40,9 +40,10 @@ namespace DualScreenDemo
|
|||||||
string randomFolderName = CreateRandomFolderAndRedirectHTML(baseDirectory);
|
string randomFolderName = CreateRandomFolderAndRedirectHTML(baseDirectory);
|
||||||
randomFolderPath = randomFolderName;
|
randomFolderPath = randomFolderName;
|
||||||
|
|
||||||
|
// 安裝包更新
|
||||||
string localAddress = GetLocalIPAddress();
|
string localAddress = GetLocalIPAddress();
|
||||||
string externalAddress = File.Exists(@"\\svr01\txt\ip.txt")
|
string externalAddress = File.Exists(@"\\JLDKTV\txt\ip.txt")
|
||||||
? File.ReadAllText(@"\\svr01\txt\ip.txt").Trim()
|
? File.ReadAllText(@"\\JLDKTV\txt\ip.txt").Trim()
|
||||||
: "";
|
: "";
|
||||||
|
|
||||||
_listener = new HttpListener();
|
_listener = new HttpListener();
|
||||||
@ -427,36 +428,50 @@ namespace DualScreenDemo
|
|||||||
switch (data.Command)
|
switch (data.Command)
|
||||||
{
|
{
|
||||||
case "pause":
|
case "pause":
|
||||||
// 执行暂停操作
|
// 执行暂停操作
|
||||||
if (VideoPlayerForm.Instance.isPaused)
|
if (VideoPlayerForm.Instance.isPaused)
|
||||||
{
|
{
|
||||||
PrimaryForm.Instance.videoPlayerForm.Play();
|
PrimaryForm.Instance.Invoke(new System.Action(() =>
|
||||||
PrimaryForm.Instance.pauseButton.Visible = true;
|
{
|
||||||
PrimaryForm.Instance.playButton.Visible = false;
|
PrimaryForm.Instance.videoPlayerForm.Play();
|
||||||
PrimaryForm.Instance.syncPauseButton.Visible = true;
|
PrimaryForm.Instance.pauseButton.Visible = true;
|
||||||
PrimaryForm.Instance.syncPlayButton.Visible = false;
|
PrimaryForm.Instance.playButton.Visible = false;
|
||||||
|
PrimaryForm.Instance.syncPauseButton.Visible = true;
|
||||||
|
PrimaryForm.Instance.syncPlayButton.Visible = false;
|
||||||
|
}));
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
PrimaryForm.Instance.videoPlayerForm.Pause();
|
PrimaryForm.Instance.Invoke(new System.Action(() =>
|
||||||
PrimaryForm.Instance.pauseButton.Visible = false;
|
{
|
||||||
PrimaryForm.Instance.playButton.Visible = true;
|
PrimaryForm.Instance.videoPlayerForm.Pause();
|
||||||
PrimaryForm.Instance.syncPauseButton.Visible = false;
|
PrimaryForm.Instance.pauseButton.Visible = false;
|
||||||
PrimaryForm.Instance.syncPlayButton.Visible = true;
|
PrimaryForm.Instance.playButton.Visible = true;
|
||||||
|
PrimaryForm.Instance.syncPauseButton.Visible = false;
|
||||||
|
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":
|
||||||
@ -485,15 +500,23 @@ 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":
|
||||||
|
@ -450,13 +450,13 @@ 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.Invoke(new System.Action(() =>
|
MainForm.BeginInvoke(new Action(() =>
|
||||||
{
|
{
|
||||||
// 清除目前歌曲的顯示標籤文字
|
if (MainForm.songDisplayLabel != null)
|
||||||
MainForm.songDisplayLabel.Text = "";
|
MainForm.songDisplayLabel.Text = "";
|
||||||
|
|
||||||
// 顯示下一首歌的標籤
|
if (MainForm.nextSongLabel != null)
|
||||||
MainForm.nextSongLabel.Visible = true;
|
MainForm.nextSongLabel.Visible = true;
|
||||||
}));
|
}));
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
|
@ -1,6 +1,10 @@
|
|||||||
|
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
|
||||||
{
|
{
|
||||||
@ -21,6 +25,28 @@ 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()
|
||||||
{
|
{
|
||||||
@ -29,9 +55,9 @@ namespace DualScreenDemo
|
|||||||
{
|
{
|
||||||
Name = "constructionButton",
|
Name = "constructionButton",
|
||||||
};
|
};
|
||||||
ConfigureButton(constructionButton, 876, 494, 148, 64,
|
string path = Path.Combine(Application.StartupPath, @"themes\superstar\場景音效\場景音效_工地.png");
|
||||||
resizedNormalStateImageForSceneSoundEffects, resizedNormalStateImageForSceneSoundEffects, resizedNormalStateImageForSceneSoundEffects,
|
ConfigureImageButton(constructionButton, 1183, 634, 148, 64,
|
||||||
ConstructionButton_Click);
|
path, ConstructionButton_Click);
|
||||||
this.Controls.Add(constructionButton);
|
this.Controls.Add(constructionButton);
|
||||||
|
|
||||||
|
|
||||||
@ -39,9 +65,9 @@ namespace DualScreenDemo
|
|||||||
{
|
{
|
||||||
Name = "marketButton",
|
Name = "marketButton",
|
||||||
};
|
};
|
||||||
ConfigureButton(marketButton, 1037, 495, 148, 63,
|
path = Path.Combine(Application.StartupPath, @"themes\superstar\場景音效\場景音效_市場.png");
|
||||||
resizedNormalStateImageForSceneSoundEffects, resizedNormalStateImageForSceneSoundEffects, resizedNormalStateImageForSceneSoundEffects,
|
ConfigureImageButton(marketButton, 1394, 634, 148, 63,
|
||||||
MarketButton_Click);
|
path, MarketButton_Click);
|
||||||
this.Controls.Add(marketButton);
|
this.Controls.Add(marketButton);
|
||||||
|
|
||||||
|
|
||||||
@ -49,9 +75,9 @@ namespace DualScreenDemo
|
|||||||
{
|
{
|
||||||
Name = "drivingButton",
|
Name = "drivingButton",
|
||||||
};
|
};
|
||||||
ConfigureButton(drivingButton, 876, 570, 148, 63,
|
path = Path.Combine(Application.StartupPath, @"themes\superstar\場景音效\場景音效_開車.png");
|
||||||
resizedNormalStateImageForSceneSoundEffects, resizedNormalStateImageForSceneSoundEffects, resizedNormalStateImageForSceneSoundEffects,
|
ConfigureImageButton(drivingButton, 1183, 720, 148, 63,
|
||||||
DrivingButton_Click);
|
path, DrivingButton_Click);
|
||||||
this.Controls.Add(drivingButton);
|
this.Controls.Add(drivingButton);
|
||||||
|
|
||||||
|
|
||||||
@ -59,9 +85,9 @@ namespace DualScreenDemo
|
|||||||
{
|
{
|
||||||
Name = "airportButton",
|
Name = "airportButton",
|
||||||
};
|
};
|
||||||
ConfigureButton(airportButton, 1037, 570, 148, 63,
|
path = Path.Combine(Application.StartupPath, @"themes\superstar\場景音效\場景音效_機場.png");
|
||||||
resizedNormalStateImageForSceneSoundEffects, resizedNormalStateImageForSceneSoundEffects, resizedNormalStateImageForSceneSoundEffects,
|
ConfigureImageButton(airportButton, 1394, 720, 148, 63,
|
||||||
AirportButton_Click);
|
path, AirportButton_Click);
|
||||||
this.Controls.Add(airportButton);
|
this.Controls.Add(airportButton);
|
||||||
|
|
||||||
|
|
||||||
@ -69,9 +95,9 @@ namespace DualScreenDemo
|
|||||||
{
|
{
|
||||||
Name = "officeButton",
|
Name = "officeButton",
|
||||||
};
|
};
|
||||||
ConfigureButton(officeButton, 876, 646, 148, 64,
|
path = Path.Combine(Application.StartupPath, @"themes\superstar\場景音效\場景音效_辦公室.png");
|
||||||
resizedNormalStateImageForSceneSoundEffects, resizedNormalStateImageForSceneSoundEffects, resizedNormalStateImageForSceneSoundEffects,
|
ConfigureImageButton(officeButton, 1183, 806, 148, 64,
|
||||||
OfficeButton_Click);
|
path, OfficeButton_Click);
|
||||||
this.Controls.Add(officeButton);
|
this.Controls.Add(officeButton);
|
||||||
|
|
||||||
|
|
||||||
@ -79,10 +105,9 @@ namespace DualScreenDemo
|
|||||||
{
|
{
|
||||||
Name = "closeButton",
|
Name = "closeButton",
|
||||||
};
|
};
|
||||||
|
path = Path.Combine(Application.StartupPath, @"themes\superstar\場景音效\場景音效_關閉.png");
|
||||||
ConfigureButton(closeButton, 1036, 646, 150, 63,
|
ConfigureImageButton(closeButton, 1394, 806, 150, 63,
|
||||||
resizedNormalStateImageForSceneSoundEffects, resizedNormalStateImageForSceneSoundEffects, resizedNormalStateImageForSceneSoundEffects,
|
path, CloseButton_Click);
|
||||||
CloseButton_Click);
|
|
||||||
this.Controls.Add(closeButton);
|
this.Controls.Add(closeButton);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -96,7 +121,7 @@ namespace DualScreenDemo
|
|||||||
|
|
||||||
if (!pictureBoxSceneSoundEffects.Visible)
|
if (!pictureBoxSceneSoundEffects.Visible)
|
||||||
{
|
{
|
||||||
ShowImageOnPictureBoxSceneSoundEffects(Path.Combine(Application.StartupPath, @"themes\superstar\555022.jpg"));
|
ShowImageOnPictureBoxSceneSoundEffects(Path.Combine(Application.StartupPath, @"themes\superstar\場景音效\場景音效.png"));
|
||||||
SetPictureBoxSceneSoundEffectsAndButtonsVisibility(true);
|
SetPictureBoxSceneSoundEffectsAndButtonsVisibility(true);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
@ -131,28 +156,30 @@ 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)
|
||||||
{
|
{
|
||||||
|
|
||||||
Bitmap originalImage = new Bitmap(imagePath);
|
if (File.Exists(imagePath))
|
||||||
|
{
|
||||||
|
// 直接載入完整圖
|
||||||
|
Bitmap image = new Bitmap(imagePath);
|
||||||
|
|
||||||
|
// 顯示在 PictureBox 上
|
||||||
Rectangle cropArea = new Rectangle(859, 427, 342, 295);
|
pictureBoxSceneSoundEffects.Image = image;
|
||||||
|
|
||||||
|
// 設定 PictureBox 的大小與位置(依你的需要調整)
|
||||||
Bitmap croppedImage = CropImage(originalImage, cropArea);
|
ResizeAndPositionPictureBox(pictureBoxSceneSoundEffects, 850, 450, image.Width , image.Height);
|
||||||
|
|
||||||
|
pictureBoxSceneSoundEffects.Visible = true;
|
||||||
pictureBoxSceneSoundEffects.Image = croppedImage;
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
ResizeAndPositionPictureBox(pictureBoxSceneSoundEffects, cropArea.X, cropArea.Y, cropArea.Width, cropArea.Height);
|
Console.WriteLine("圖片檔案不存在:" + imagePath);
|
||||||
|
}
|
||||||
pictureBoxSceneSoundEffects.Visible = true;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void TogglePictureBoxSceneSoundEffectsButtonsVisibility()
|
private void TogglePictureBoxSceneSoundEffectsButtonsVisibility()
|
||||||
|
@ -116,18 +116,19 @@ namespace DualScreenDemo
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
private void VodButton_Click(object sender, EventArgs e)
|
private async void VodButton_Click(object sender, EventArgs e)
|
||||||
{
|
{
|
||||||
|
|
||||||
OverlayForm.MainForm.AddSongToPlaylist(currentSelectedSong);
|
|
||||||
SetVodScreenPictureBoxAndButtonsVisibility(false);
|
SetVodScreenPictureBoxAndButtonsVisibility(false);
|
||||||
|
await Task.Delay(1000);
|
||||||
|
OverlayForm.MainForm.AddSongToPlaylist(currentSelectedSong);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void InsertButton_Click(object sender, EventArgs e)
|
private void InsertButton_Click(object sender, EventArgs e)
|
||||||
{
|
{
|
||||||
|
|
||||||
OverlayForm.MainForm.InsertSongToPlaylist(currentSelectedSong);
|
|
||||||
SetVodScreenPictureBoxAndButtonsVisibility(false);
|
SetVodScreenPictureBoxAndButtonsVisibility(false);
|
||||||
|
OverlayForm.MainForm.InsertSongToPlaylist(currentSelectedSong);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void AlbumButton_Click(object sender, EventArgs e)
|
private void AlbumButton_Click(object sender, EventArgs e)
|
||||||
|
@ -242,6 +242,23 @@ 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")]
|
||||||
@ -940,12 +957,12 @@ namespace DualScreenDemo
|
|||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
MessageBox.Show("Failed to send command: " + ex.Message);
|
Console.WriteLine("Failed to send command: " + ex.Message);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
MessageBox.Show("Serial port is not open.");
|
Console.WriteLine("Serial port is not open.");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1560,7 +1577,7 @@ namespace DualScreenDemo
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// 2. 比較本地和服務器文件夾
|
// 2. 確認本地文件夾是否存在(不存在則創立)
|
||||||
if (!Directory.Exists(localVideoPath))
|
if (!Directory.Exists(localVideoPath))
|
||||||
{
|
{
|
||||||
Directory.CreateDirectory(localVideoPath);
|
Directory.CreateDirectory(localVideoPath);
|
||||||
@ -1575,7 +1592,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. 檢查並更新文件
|
// 3-1. 檢查並更新文件
|
||||||
foreach (var serverFile in serverFiles)
|
foreach (var serverFile in serverFiles)
|
||||||
{
|
{
|
||||||
bool needsCopy = false;
|
bool needsCopy = false;
|
||||||
@ -1607,7 +1624,22 @@ 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();
|
||||||
}
|
}
|
||||||
@ -2061,14 +2093,14 @@ namespace DualScreenDemo
|
|||||||
videoPlayerForm.ReplayCurrentSong();
|
videoPlayerForm.ReplayCurrentSong();
|
||||||
}
|
}
|
||||||
|
|
||||||
private void PauseButton_Click(object sender, EventArgs e)
|
public void PauseButton_Click(object sender, EventArgs e)
|
||||||
{
|
{
|
||||||
videoPlayerForm.Pause();
|
videoPlayerForm.Pause();
|
||||||
pauseButton.Visible = false;
|
pauseButton.Visible = false;
|
||||||
playButton.Visible = true;
|
playButton.Visible = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void PlayButton_Click(object sender, EventArgs e)
|
public void PlayButton_Click(object sender, EventArgs e)
|
||||||
{
|
{
|
||||||
videoPlayerForm.Play();
|
videoPlayerForm.Play();
|
||||||
playButton.Visible = false;
|
playButton.Visible = false;
|
||||||
|
48
Program.cs
48
Program.cs
@ -2,7 +2,7 @@ using System.IO;
|
|||||||
using Microsoft.Win32;
|
using Microsoft.Win32;
|
||||||
using System.Diagnostics;
|
using System.Diagnostics;
|
||||||
using DBObj;
|
using DBObj;
|
||||||
using HeartbeatSender;
|
using DataCheck;
|
||||||
|
|
||||||
namespace DualScreenDemo
|
namespace DualScreenDemo
|
||||||
{
|
{
|
||||||
@ -17,16 +17,28 @@ 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("正在與中控取得聯繫...");
|
||||||
|
var sender = new HeartbeatSender.heartbeatSender();
|
||||||
|
|
||||||
Console.WriteLine("正在與中控取得聯繫");
|
|
||||||
/*var sender = new HeartbeatSender.heartbeatSender();
|
|
||||||
|
|
||||||
// 同步呼叫非同步登入取得 token
|
// 同步呼叫非同步登入取得 token
|
||||||
bool loginSuccess = sender.LoginAndGetTokenAsync().GetAwaiter().GetResult();
|
bool loginSuccess = sender.LoginAndGetTokenAsync().GetAwaiter().GetResult();
|
||||||
|
|
||||||
@ -35,21 +47,26 @@ namespace DualScreenDemo
|
|||||||
// 先送一次心跳 (同步呼叫)
|
// 先送一次心跳 (同步呼叫)
|
||||||
sender.SendHeartbeatAsync().GetAwaiter().GetResult();
|
sender.SendHeartbeatAsync().GetAwaiter().GetResult();
|
||||||
|
|
||||||
// 背景持續每3秒送心跳
|
// 背景持續每5分鐘送心跳
|
||||||
_ = Task.Run(async () =>
|
_ = Task.Run(async () =>
|
||||||
{
|
{
|
||||||
while (true)
|
while (true)
|
||||||
{
|
{
|
||||||
await sender.SendHeartbeatAsync();
|
await sender.SendHeartbeatAsync();
|
||||||
await Task.Delay(300000); // 每3秒送一次
|
await Task.Delay(300000); // 每5min送一次
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
Console.WriteLine("正在發送心跳中...");
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
Console.WriteLine("登入失敗,無法送出心跳");
|
Console.WriteLine("登入失敗,無法送出心跳");
|
||||||
}
|
}
|
||||||
*/
|
//之後需要做添加同步菜單+酒單+背景圖(若圖規格有做正規化)
|
||||||
|
// DataCheck.cs 有預寫好相關流程函式
|
||||||
|
Console.WriteLine("更新菜單和酒單...");
|
||||||
|
dataCheck Checkprocess = new dataCheck();
|
||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
// COM 初始化
|
// COM 初始化
|
||||||
@ -62,7 +79,7 @@ namespace DualScreenDemo
|
|||||||
// 初始化管理器
|
// 初始化管理器
|
||||||
|
|
||||||
songListManager = new SongListManager(); // 使用单例
|
songListManager = new SongListManager(); // 使用单例
|
||||||
//artistManager = new ArtistManager();
|
//artistManager = new ArtistManager();
|
||||||
|
|
||||||
var commandHandler = new CommandHandler(songListManager);
|
var commandHandler = new CommandHandler(songListManager);
|
||||||
serialPortManager = new SerialPortManager(commandHandler);
|
serialPortManager = new SerialPortManager(commandHandler);
|
||||||
@ -106,9 +123,22 @@ 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)
|
||||||
{
|
{
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
using System.IO.Ports;
|
using System.IO.Ports;
|
||||||
using System.Text;
|
using System.Text;
|
||||||
|
using System.Collections.Concurrent;
|
||||||
|
|
||||||
namespace DualScreenDemo
|
namespace DualScreenDemo
|
||||||
{
|
{
|
||||||
@ -48,18 +49,26 @@ namespace DualScreenDemo
|
|||||||
|
|
||||||
// 綁定資料接收事件
|
// 綁定資料接收事件
|
||||||
mySerialPort.DataReceived += new SerialDataReceivedEventHandler(DataReceivedHandler);
|
mySerialPort.DataReceived += new SerialDataReceivedEventHandler(DataReceivedHandler);
|
||||||
|
AppDomain.CurrentDomain.ProcessExit += (s, e) =>
|
||||||
|
{
|
||||||
|
cts.Cancel();
|
||||||
|
};
|
||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
mySerialPort.Open();
|
mySerialPort.Open();
|
||||||
Console.WriteLine($"{selectedPort} 串列埠已成功開啟。");
|
Console.WriteLine($"{selectedPort} 串列埠已成功開啟。");
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
MessageBox.Show($"開啟 {selectedPort} 串列埠時發生錯誤: {ex.Message}");
|
MessageBox.Show($"開啟 {selectedPort} 串列埠時發生錯誤: {ex.Message}");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
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)
|
||||||
{
|
{
|
||||||
@ -68,19 +77,14 @@ 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++)
|
||||||
{
|
{
|
||||||
@ -88,31 +92,46 @@ 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)
|
||||||
{
|
{
|
||||||
try
|
isProcessing = true;
|
||||||
{
|
Task.Run(ProcessQueueAsync);
|
||||||
commandHandler.ProcessData(indata);
|
}
|
||||||
}
|
|
||||||
catch (Exception processEx)
|
|
||||||
{
|
|
||||||
Console.WriteLine($"處理資料時發生錯誤: {processEx.Message}");
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
// Console.WriteLine("未接收到任何資料。");
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
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;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
@ -3,6 +3,8 @@ 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
|
||||||
@ -19,12 +21,12 @@ namespace DualScreenDemo
|
|||||||
return cp;
|
return cp;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
#endregion
|
#endregion
|
||||||
|
|
||||||
|
|
||||||
// 单例实例
|
// 单例实例
|
||||||
public static VideoPlayerForm Instance { get; private set; }
|
public static VideoPlayerForm Instance { get; private set; }
|
||||||
|
|
||||||
// 导入user32.dll API
|
// 导入user32.dll API
|
||||||
[DllImport("user32.dll")]
|
[DllImport("user32.dll")]
|
||||||
static extern bool SetWindowPos(IntPtr hWnd, IntPtr hWndInsertAfter, int X, int Y, int cx, int cy, uint uFlags);
|
static extern bool SetWindowPos(IntPtr hWnd, IntPtr hWndInsertAfter, int X, int Y, int cx, int cy, uint uFlags);
|
||||||
@ -150,7 +152,7 @@ namespace DualScreenDemo
|
|||||||
{
|
{
|
||||||
if (secondMonitor != null)
|
if (secondMonitor != null)
|
||||||
{
|
{
|
||||||
SetWindowPos(this.Handle, IntPtr.Zero, secondMonitor.Bounds.X, secondMonitor.Bounds.Y,
|
SetWindowPos(this.Handle, IntPtr.Zero, secondMonitor.Bounds.X, secondMonitor.Bounds.Y,
|
||||||
secondMonitor.Bounds.Width, secondMonitor.Bounds.Height, 0);
|
secondMonitor.Bounds.Width, secondMonitor.Bounds.Height, 0);
|
||||||
}
|
}
|
||||||
IntPtr exStyle = GetWindowLong(this.Handle, GWL_EXSTYLE);
|
IntPtr exStyle = GetWindowLong(this.Handle, GWL_EXSTYLE);
|
||||||
@ -394,7 +396,7 @@ namespace DualScreenDemo
|
|||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
Console.WriteLine($"Exception in AddFilterByClsid: {ex.Message}");
|
Console.WriteLine($"Exception in AddFilterByClsid: {ex.Message}");
|
||||||
throw; // Rethrow the exception to handle it further up the call stack
|
throw; // Rethrow the exception to handle it further up the call stack
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -454,12 +456,14 @@ namespace DualScreenDemo
|
|||||||
// 計算縮放後的尺寸(但起點仍是 0,0)
|
// 計算縮放後的尺寸(但起點仍是 0,0)
|
||||||
int newWidth = (int)(designWidth * scale);
|
int newWidth = (int)(designWidth * scale);
|
||||||
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;
|
||||||
@ -479,7 +483,7 @@ namespace DualScreenDemo
|
|||||||
MessageBox.Show(String.Format("Error syncing to primary monitor: {0}", ex.Message));
|
MessageBox.Show(String.Format("Error syncing to primary monitor: {0}", ex.Message));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
public void ClosePrimaryScreenPanel()
|
public void ClosePrimaryScreenPanel()
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
@ -547,7 +551,7 @@ namespace DualScreenDemo
|
|||||||
// [2] 停止當前播放,釋放資源(例如關閉影片播放器、清除緩衝)
|
// [2] 停止當前播放,釋放資源(例如關閉影片播放器、清除緩衝)
|
||||||
StopAndReleaseResources();
|
StopAndReleaseResources();
|
||||||
|
|
||||||
|
|
||||||
// [3] 將新的播放清單指派給 `playingSongList`
|
// [3] 將新的播放清單指派給 `playingSongList`
|
||||||
playingSongList = songList;
|
playingSongList = songList;
|
||||||
|
|
||||||
@ -561,7 +565,7 @@ namespace DualScreenDemo
|
|||||||
|
|
||||||
// [6] 若使用者點播清單有歌,就開始播放
|
// [6] 若使用者點播清單有歌,就開始播放
|
||||||
if (isUserPlaylistPlaying)
|
if (isUserPlaylistPlaying)
|
||||||
{
|
{
|
||||||
// [6.1] 設定當前歌曲索引為 -1(意味著即將播放第一首,從 `PlayNextSong` 開始)
|
// [6.1] 設定當前歌曲索引為 -1(意味著即將播放第一首,從 `PlayNextSong` 開始)
|
||||||
currentSongIndex = -1;
|
currentSongIndex = -1;
|
||||||
// [6.2] 播放下一首歌(實際會遞增 index 為 0,並播放該首歌)
|
// [6.2] 播放下一首歌(實際會遞增 index 為 0,並播放該首歌)
|
||||||
@ -577,14 +581,14 @@ namespace DualScreenDemo
|
|||||||
public async Task PlayPublicPlaylist()
|
public async Task PlayPublicPlaylist()
|
||||||
{
|
{
|
||||||
Console.WriteLine("開始播放公播清單...");
|
Console.WriteLine("開始播放公播清單...");
|
||||||
|
|
||||||
// 在切换到公播之前,确保最后一首用户歌曲状态正确
|
// 在切换到公播之前,确保最后一首用户歌曲状态正确
|
||||||
if (PrimaryForm.currentSongIndexInHistory >= 0 &&
|
if (PrimaryForm.currentSongIndexInHistory >= 0 &&
|
||||||
PrimaryForm.currentSongIndexInHistory < PrimaryForm.playStates.Count)
|
PrimaryForm.currentSongIndexInHistory < PrimaryForm.playStates.Count)
|
||||||
{
|
{
|
||||||
PrimaryForm.playStates[PrimaryForm.currentSongIndexInHistory] = PlayState.Played;
|
PrimaryForm.playStates[PrimaryForm.currentSongIndexInHistory] = PlayState.Played;
|
||||||
Console.WriteLine($"切換到公播前更新最後一首歌曲狀態為已播放,索引:{PrimaryForm.currentSongIndexInHistory}");
|
Console.WriteLine($"切換到公播前更新最後一首歌曲狀態為已播放,索引:{PrimaryForm.currentSongIndexInHistory}");
|
||||||
|
|
||||||
// 强制刷新显示
|
// 强制刷新显示
|
||||||
if (PrimaryForm.Instance.multiPagePanel != null)
|
if (PrimaryForm.Instance.multiPagePanel != null)
|
||||||
{
|
{
|
||||||
@ -594,12 +598,12 @@ namespace DualScreenDemo
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
isUserPlaylistPlaying = false;
|
isUserPlaylistPlaying = false;
|
||||||
IsPlayingPublicSong = true; // 设置为正在播放公播
|
IsPlayingPublicSong = true; // 设置为正在播放公播
|
||||||
currentSongIndex = -1;
|
currentSongIndex = -1;
|
||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
// 重新整理公播清單
|
// 重新整理公播清單
|
||||||
publicPlaylist = new List<SongData>();
|
publicPlaylist = new List<SongData>();
|
||||||
@ -609,7 +613,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
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
@ -621,8 +625,8 @@ namespace DualScreenDemo
|
|||||||
if (File.Exists(bgmPath))
|
if (File.Exists(bgmPath))
|
||||||
{
|
{
|
||||||
publicPlaylist.Add(new SongData(
|
publicPlaylist.Add(new SongData(
|
||||||
i.ToString(), $"背景音樂{i:D2}",
|
i.ToString(), $"背景音樂{i:D2}",
|
||||||
"", "", "",bgmPath, "", "", "", "", 1
|
"", "", "", bgmPath, "", "", "", "", 1
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -678,7 +682,8 @@ 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);
|
||||||
}));
|
}));
|
||||||
}
|
}
|
||||||
@ -691,7 +696,8 @@ namespace DualScreenDemo
|
|||||||
// 重置跑马灯文本
|
// 重置跑马灯文本
|
||||||
if (overlayForm.InvokeRequired)
|
if (overlayForm.InvokeRequired)
|
||||||
{
|
{
|
||||||
overlayForm.Invoke(new MethodInvoker(() => {
|
overlayForm.Invoke(new MethodInvoker(() =>
|
||||||
|
{
|
||||||
overlayForm.ResetMarqueeTextToWelcomeMessage();
|
overlayForm.ResetMarqueeTextToWelcomeMessage();
|
||||||
}));
|
}));
|
||||||
}
|
}
|
||||||
@ -735,7 +741,7 @@ namespace DualScreenDemo
|
|||||||
|
|
||||||
// 根據目前播放模式(點歌 or 公播)決定要播放的清單
|
// 根據目前播放模式(點歌 or 公播)決定要播放的清單
|
||||||
List<SongData> currentPlaylist = isUserPlaylistPlaying ? playingSongList : publicPlaylist;
|
List<SongData> currentPlaylist = isUserPlaylistPlaying ? playingSongList : publicPlaylist;
|
||||||
|
|
||||||
// 若播放清單是空的,直接返回(不執行播放)
|
// 若播放清單是空的,直接返回(不執行播放)
|
||||||
if (!currentPlaylist.Any()) return;
|
if (!currentPlaylist.Any()) return;
|
||||||
|
|
||||||
@ -760,30 +766,30 @@ namespace DualScreenDemo
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
// 可以取得 燈控/聲控 的位置
|
// 可以取得 燈控/聲控 的位置
|
||||||
var songToPlay = currentPlaylist[currentSongIndex];
|
var songToPlay = currentPlaylist[currentSongIndex];
|
||||||
|
|
||||||
// pathToPlay 需要調整
|
|
||||||
var pathToPlay = File.Exists(songToPlay.SongFilePathHost1) ? songToPlay.SongFilePathHost1 : songToPlay.SongFilePathHost2;
|
|
||||||
|
|
||||||
// 若兩個 host 上都找不到檔案就直接結束
|
// pathToPlay 需要調整
|
||||||
if (!File.Exists(pathToPlay))
|
var pathToPlay = File.Exists(songToPlay.SongFilePathHost1) ? songToPlay.SongFilePathHost1 : songToPlay.SongFilePathHost2;
|
||||||
{
|
|
||||||
Console.WriteLine($"文件不存在:{pathToPlay}");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// 更新目前正在播放的歌曲
|
// 若兩個 host 上都找不到檔案就直接結束
|
||||||
currentPlayingSong = songToPlay;
|
if (!File.Exists(pathToPlay))
|
||||||
|
{
|
||||||
|
Console.WriteLine($"文件不存在:{pathToPlay}");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
// 更新畫面上顯示的下一首歌資訊
|
// 更新目前正在播放的歌曲
|
||||||
UpdateNextSongFromPlaylist();
|
currentPlayingSong = songToPlay;
|
||||||
|
|
||||||
// 顯示 QRCode(可能是點歌頁用)
|
// 更新畫面上顯示的下一首歌資訊
|
||||||
overlayForm.DisplayQRCodeOnOverlay(HttpServer.randomFolderPath);
|
UpdateNextSongFromPlaylist();
|
||||||
|
|
||||||
// 隱藏「暫停中」標籤
|
// 顯示 QRCode(可能是點歌頁用)
|
||||||
overlayForm.HidePauseLabel();
|
overlayForm.DisplayQRCodeOnOverlay(HttpServer.randomFolderPath);
|
||||||
|
|
||||||
|
// 隱藏「暫停中」標籤
|
||||||
|
overlayForm.HidePauseLabel();
|
||||||
|
|
||||||
|
|
||||||
try
|
try
|
||||||
@ -810,7 +816,7 @@ namespace DualScreenDemo
|
|||||||
await Task.Delay(1000);
|
await Task.Delay(1000);
|
||||||
StopAndReleaseResources();
|
StopAndReleaseResources();
|
||||||
await Task.Delay(1000);
|
await Task.Delay(1000);
|
||||||
|
|
||||||
// 重新初始化 COM
|
// 重新初始化 COM
|
||||||
int hr = CoInitializeEx(IntPtr.Zero, COINIT.APARTMENTTHREADED);
|
int hr = CoInitializeEx(IntPtr.Zero, COINIT.APARTMENTTHREADED);
|
||||||
if (hr >= 0)
|
if (hr >= 0)
|
||||||
@ -872,14 +878,14 @@ namespace DualScreenDemo
|
|||||||
{
|
{
|
||||||
SyncToPrimaryMonitor();
|
SyncToPrimaryMonitor();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 跳至下一首歌曲的方法,會根據目前播放清單(使用者清單或公播清單)做切換與播放邏輯控制。
|
/// 跳至下一首歌曲的方法,會根據目前播放清單(使用者清單或公播清單)做切換與播放邏輯控制。
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public async Task SkipToNextSong()
|
public async Task SkipToNextSong()
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
// 停止當前播放並釋放資源(如播放器、影片檔等)
|
// 停止當前播放並釋放資源(如播放器、影片檔等)
|
||||||
StopAndReleaseResources();
|
StopAndReleaseResources();
|
||||||
@ -918,21 +924,22 @@ namespace DualScreenDemo
|
|||||||
}
|
}
|
||||||
|
|
||||||
// 如果目前歌曲在播放歷史列表的索引是合法的(防呆)
|
// 如果目前歌曲在播放歷史列表的索引是合法的(防呆)
|
||||||
if (PrimaryForm.currentSongIndexInHistory >= 0 && PrimaryForm.currentSongIndexInHistory < PrimaryForm.playedSongsHistory.Count)
|
if (PrimaryForm.currentSongIndexInHistory >= 0 && PrimaryForm.currentSongIndexInHistory < PrimaryForm.playedSongsHistory.Count)
|
||||||
{
|
{
|
||||||
var currentSong = PrimaryForm.playedSongsHistory[PrimaryForm.currentSongIndexInHistory];
|
var currentSong = PrimaryForm.playedSongsHistory[PrimaryForm.currentSongIndexInHistory];
|
||||||
|
|
||||||
// 如果新的播放清單還有歌曲,並且當前歷史記錄中的歌曲與播放清單的第一首一致,則標記為已播畢
|
// 如果新的播放清單還有歌曲,並且當前歷史記錄中的歌曲與播放清單的第一首一致,則標記為已播畢
|
||||||
if (playingSongList.Count > 0 && currentSong == playingSongList[0])
|
if (playingSongList.Count > 0 && currentSong == playingSongList[0])
|
||||||
{
|
{
|
||||||
PrimaryForm.playStates[PrimaryForm.currentSongIndexInHistory] = PlayState.Played;
|
PrimaryForm.playStates[PrimaryForm.currentSongIndexInHistory] = PlayState.Played;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
/*如果當前為公播,不可以+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);
|
||||||
}
|
}
|
||||||
@ -952,7 +959,7 @@ namespace DualScreenDemo
|
|||||||
{
|
{
|
||||||
isUserPlaylistPlaying = false;
|
isUserPlaylistPlaying = false;
|
||||||
currentSongIndex = -1;
|
currentSongIndex = -1;
|
||||||
|
|
||||||
// 重新初始化公播列表
|
// 重新初始化公播列表
|
||||||
publicPlaylist = new List<SongData>();
|
publicPlaylist = new List<SongData>();
|
||||||
|
|
||||||
@ -972,7 +979,7 @@ namespace DualScreenDemo
|
|||||||
if (File.Exists(bgmPath))
|
if (File.Exists(bgmPath))
|
||||||
{
|
{
|
||||||
publicPlaylist.Add(new SongData(
|
publicPlaylist.Add(new SongData(
|
||||||
i.ToString(), $"背景音樂{i:D2}", "", "", "", bgmPath,
|
i.ToString(), $"背景音樂{i:D2}", "", "", "", bgmPath,
|
||||||
"", "", "", "", 1
|
"", "", "", "", 1
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
@ -1273,7 +1280,7 @@ namespace DualScreenDemo
|
|||||||
{
|
{
|
||||||
long currentPosition = 0;
|
long currentPosition = 0;
|
||||||
long duration = 0;
|
long duration = 0;
|
||||||
|
|
||||||
if (mediaSeekingSecondary.GetCurrentPosition(out currentPosition) >= 0 &&
|
if (mediaSeekingSecondary.GetCurrentPosition(out currentPosition) >= 0 &&
|
||||||
mediaSeekingSecondary.GetDuration(out duration) >= 0)
|
mediaSeekingSecondary.GetDuration(out duration) >= 0)
|
||||||
{
|
{
|
||||||
@ -1281,24 +1288,24 @@ namespace DualScreenDemo
|
|||||||
double durationSeconds = duration / 10000000.0;
|
double durationSeconds = duration / 10000000.0;
|
||||||
|
|
||||||
// 添加更严格的结束条件判断
|
// 添加更严格的结束条件判断
|
||||||
bool isAtEnd = durationSeconds > 0 && currentSeconds > 0 &&
|
bool isAtEnd = durationSeconds > 0 && currentSeconds > 0 &&
|
||||||
Math.Abs(currentSeconds - durationSeconds) < 0.1 && // 确保真的到了结尾
|
Math.Abs(currentSeconds - durationSeconds) < 0.1 && // 确保真的到了结尾
|
||||||
!isPaused;
|
!isPaused;
|
||||||
|
|
||||||
if (isAtEnd && !isPlayingNext)
|
if (isAtEnd && !isPlayingNext)
|
||||||
{
|
{
|
||||||
Console.WriteLine($"檢測到歌曲結束 - 當前位置: {currentSeconds:F2}秒, 總時長: {durationSeconds:F2}秒");
|
Console.WriteLine($"檢測到歌曲結束 - 當前位置: {currentSeconds:F2}秒, 總時長: {durationSeconds:F2}秒");
|
||||||
|
|
||||||
if (!isPlayingNext)
|
if (!isPlayingNext)
|
||||||
{
|
{
|
||||||
isPlayingNext = true;
|
isPlayingNext = true;
|
||||||
|
|
||||||
// 添加额外的保护:确保在切换前停止当前播放
|
// 添加额外的保护:确保在切换前停止当前播放
|
||||||
if (mediaControlSecondary != null)
|
if (mediaControlSecondary != null)
|
||||||
{
|
{
|
||||||
mediaControlSecondary.Stop();
|
mediaControlSecondary.Stop();
|
||||||
}
|
}
|
||||||
|
|
||||||
this.BeginInvoke(new Action(async () =>
|
this.BeginInvoke(new Action(async () =>
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
@ -1311,17 +1318,17 @@ namespace DualScreenDemo
|
|||||||
{
|
{
|
||||||
if (playingSongList.Count > 0)
|
if (playingSongList.Count > 0)
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
// 移除當前播放的歌曲
|
// 移除當前播放的歌曲
|
||||||
playingSongList.RemoveAt(0);
|
playingSongList.RemoveAt(0);
|
||||||
|
|
||||||
// 更新播放状态逻辑
|
// 更新播放状态逻辑
|
||||||
if (PrimaryForm.currentSongIndexInHistory >= 0)
|
if (PrimaryForm.currentSongIndexInHistory >= 0)
|
||||||
{
|
{
|
||||||
// 将当前播放的歌曲标记为已播放
|
// 将当前播放的歌曲标记为已播放
|
||||||
PrimaryForm.playStates[PrimaryForm.currentSongIndexInHistory] = PlayState.Played;
|
PrimaryForm.playStates[PrimaryForm.currentSongIndexInHistory] = PlayState.Played;
|
||||||
|
|
||||||
// 如果还有下一首歌
|
// 如果还有下一首歌
|
||||||
if (playingSongList.Count > 0)
|
if (playingSongList.Count > 0)
|
||||||
{
|
{
|
||||||
@ -1337,14 +1344,14 @@ namespace DualScreenDemo
|
|||||||
if (playingSongList.Count == 0)
|
if (playingSongList.Count == 0)
|
||||||
{
|
{
|
||||||
Console.WriteLine("用戶播放列表已播放完畢,切換到公共播放列表");
|
Console.WriteLine("用戶播放列表已播放完畢,切換到公共播放列表");
|
||||||
|
|
||||||
// 确保当前歌曲状态更新为已播放
|
// 确保当前歌曲状态更新为已播放
|
||||||
if (PrimaryForm.currentSongIndexInHistory >= 0 &&
|
if (PrimaryForm.currentSongIndexInHistory >= 0 &&
|
||||||
PrimaryForm.currentSongIndexInHistory < PrimaryForm.playStates.Count)
|
PrimaryForm.currentSongIndexInHistory < PrimaryForm.playStates.Count)
|
||||||
{
|
{
|
||||||
PrimaryForm.playStates[PrimaryForm.currentSongIndexInHistory] = PlayState.Played;
|
PrimaryForm.playStates[PrimaryForm.currentSongIndexInHistory] = PlayState.Played;
|
||||||
Console.WriteLine($"已將最後一首歌曲狀態更新為已播放,索引:{PrimaryForm.currentSongIndexInHistory}");
|
Console.WriteLine($"已將最後一首歌曲狀態更新為已播放,索引:{PrimaryForm.currentSongIndexInHistory}");
|
||||||
|
|
||||||
// 强制刷新显示
|
// 强制刷新显示
|
||||||
if (PrimaryForm.Instance.multiPagePanel != null)
|
if (PrimaryForm.Instance.multiPagePanel != null)
|
||||||
{
|
{
|
||||||
@ -1360,7 +1367,7 @@ namespace DualScreenDemo
|
|||||||
currentSongIndex = -1;
|
currentSongIndex = -1;
|
||||||
|
|
||||||
// 确保所有未播放的歌曲状态被清除
|
// 确保所有未播放的歌曲状态被清除
|
||||||
for (int i = PrimaryForm.currentSongIndexInHistory + 1;
|
for (int i = PrimaryForm.currentSongIndexInHistory + 1;
|
||||||
i < PrimaryForm.playStates.Count; i++)
|
i < PrimaryForm.playStates.Count; i++)
|
||||||
{
|
{
|
||||||
if (PrimaryForm.playStates[i] == PlayState.Playing)
|
if (PrimaryForm.playStates[i] == PlayState.Playing)
|
||||||
@ -1417,14 +1424,14 @@ namespace DualScreenDemo
|
|||||||
{
|
{
|
||||||
Console.WriteLine($"監控媒體事件時發生錯誤: {ex.Message}");
|
Console.WriteLine($"監控媒體事件時發生錯誤: {ex.Message}");
|
||||||
isPlayingNext = false;
|
isPlayingNext = false;
|
||||||
|
|
||||||
// 添加重试机制
|
// 添加重试机制
|
||||||
if (!isUserPlaylistPlaying && publicPlaylist != null)
|
if (!isUserPlaylistPlaying && publicPlaylist != null)
|
||||||
{
|
{
|
||||||
await Task.Delay(1000);
|
await Task.Delay(1000);
|
||||||
await PlayNextSong();
|
await PlayNextSong();
|
||||||
}
|
}
|
||||||
|
|
||||||
await Task.Delay(1000);
|
await Task.Delay(1000);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1533,7 +1540,7 @@ namespace DualScreenDemo
|
|||||||
return -10000;
|
return -10000;
|
||||||
}
|
}
|
||||||
private bool isVocalRemoved = false;
|
private bool isVocalRemoved = false;
|
||||||
public async void ToggleVocalRemoval()
|
public async void ToggleVocalRemoval()
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
@ -1602,7 +1609,7 @@ namespace DualScreenDemo
|
|||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
Console.WriteLine( ex.Message);
|
Console.WriteLine(ex.Message);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1649,7 +1656,7 @@ namespace DualScreenDemo
|
|||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
Console.WriteLine( ex.Message);
|
Console.WriteLine(ex.Message);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1688,7 +1695,7 @@ namespace DualScreenDemo
|
|||||||
// 如果是空列表,设置为正在播放
|
// 如果是空列表,设置为正在播放
|
||||||
PrimaryForm.playStates.Add(PlayState.Playing);
|
PrimaryForm.playStates.Add(PlayState.Playing);
|
||||||
PrimaryForm.currentSongIndexInHistory = PrimaryForm.playedSongsHistory.Count - 1;
|
PrimaryForm.currentSongIndexInHistory = PrimaryForm.playedSongsHistory.Count - 1;
|
||||||
|
|
||||||
// 确保之前相同歌曲的状态为已播放
|
// 确保之前相同歌曲的状态为已播放
|
||||||
foreach (int index in sameNameIndices)
|
foreach (int index in sameNameIndices)
|
||||||
{
|
{
|
||||||
@ -1697,14 +1704,14 @@ namespace DualScreenDemo
|
|||||||
PrimaryForm.playStates[index] = PlayState.Played;
|
PrimaryForm.playStates[index] = PlayState.Played;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
VideoPlayerForm.Instance.SetPlayingSongList(PrimaryForm.userRequestedSongs);
|
VideoPlayerForm.Instance.SetPlayingSongList(PrimaryForm.userRequestedSongs);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
// 如果不是空列表,设置为未播放
|
// 如果不是空列表,设置为未播放
|
||||||
PrimaryForm.playStates.Add(PlayState.NotPlayed);
|
PrimaryForm.playStates.Add(PlayState.NotPlayed);
|
||||||
|
|
||||||
// 更新所有相同歌曲的状态
|
// 更新所有相同歌曲的状态
|
||||||
foreach (int index in sameNameIndices.Where(i => i < PrimaryForm.playedSongsHistory.Count - 1))
|
foreach (int index in sameNameIndices.Where(i => i < PrimaryForm.playedSongsHistory.Count - 1))
|
||||||
{
|
{
|
||||||
@ -1767,7 +1774,7 @@ namespace DualScreenDemo
|
|||||||
PrimaryForm.playedSongsHistory.Add(songData);
|
PrimaryForm.playedSongsHistory.Add(songData);
|
||||||
PrimaryForm.playStates.Add(PlayState.Playing);
|
PrimaryForm.playStates.Add(PlayState.Playing);
|
||||||
PrimaryForm.currentSongIndexInHistory = PrimaryForm.playedSongsHistory.Count - 1;
|
PrimaryForm.currentSongIndexInHistory = PrimaryForm.playedSongsHistory.Count - 1;
|
||||||
|
|
||||||
// 更新之前相同歌曲的状态
|
// 更新之前相同歌曲的状态
|
||||||
foreach (int index in sameNameIndices)
|
foreach (int index in sameNameIndices)
|
||||||
{
|
{
|
||||||
@ -1776,18 +1783,18 @@ namespace DualScreenDemo
|
|||||||
PrimaryForm.playStates[index] = PlayState.Played;
|
PrimaryForm.playStates[index] = PlayState.Played;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
VideoPlayerForm.Instance.SetPlayingSongList(PrimaryForm.userRequestedSongs);
|
VideoPlayerForm.Instance.SetPlayingSongList(PrimaryForm.userRequestedSongs);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
// 插入到当前播放歌曲之后
|
// 插入到当前播放歌曲之后
|
||||||
int insertIndex = PrimaryForm.currentSongIndexInHistory + 1;
|
int insertIndex = PrimaryForm.currentSongIndexInHistory + 1;
|
||||||
|
|
||||||
PrimaryForm.userRequestedSongs.Insert(1, songData);
|
PrimaryForm.userRequestedSongs.Insert(1, songData);
|
||||||
PrimaryForm.playedSongsHistory.Insert(insertIndex, songData);
|
PrimaryForm.playedSongsHistory.Insert(insertIndex, songData);
|
||||||
PrimaryForm.playStates.Insert(insertIndex, PlayState.NotPlayed);
|
PrimaryForm.playStates.Insert(insertIndex, PlayState.NotPlayed);
|
||||||
|
|
||||||
// 更新所有相同歌曲的状态
|
// 更新所有相同歌曲的状态
|
||||||
foreach (int index in sameNameIndices)
|
foreach (int index in sameNameIndices)
|
||||||
{
|
{
|
||||||
@ -1817,5 +1824,67 @@ 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;
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
84
WatchDog.cs
Normal file
84
WatchDog.cs
Normal file
@ -0,0 +1,84 @@
|
|||||||
|
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}");
|
||||||
|
}
|
||||||
|
}
|
@ -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';
|
||||||
});
|
});
|
||||||
|
@ -80,26 +80,20 @@
|
|||||||
}
|
}
|
||||||
.content {
|
.content {
|
||||||
display: grid;
|
display: grid;
|
||||||
grid-template-columns: repeat(3, 1fr); /* 固定3欄 */
|
grid-template-columns: repeat(auto-fill, minmax(100px, 1fr));
|
||||||
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); /* 固定3欄 */
|
grid-template-columns: repeat(3, 1fr);
|
||||||
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: 130px;
|
width: 100%;
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
|
Loading…
x
Reference in New Issue
Block a user