新增看門狗

This commit is contained in:
jasonchenwork 2025-06-20 13:12:10 +08:00
parent 39c2a39fd8
commit 4d09ed914a
4 changed files with 278 additions and 83 deletions

10
DualScreenDemo.Shared.cs Normal file
View 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; }
}
}

View File

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

View File

@ -3,6 +3,8 @@ using System.Runtime.InteropServices;
using DirectShowLib;
using DBObj;
using OverlayFormObj;
using DualScreenDemo.Shared;
namespace DualScreenDemo
{
public class VideoPlayerForm : Form
@ -456,10 +458,12 @@ namespace DualScreenDemo
int newHeight = (int)(designHeight * scale);
// trash location with trash flexible of screen size.
if (actualWidth == 1024){
if (actualWidth == 1024)
{
newWidth = (int)(newWidth * 0.84f);
}
else if (actualWidth == 1440){
else if (actualWidth == 1440)
{
newWidth = (int)(newWidth * 0.9f);
}
videoWindowPrimary = (IVideoWindow)videoRendererPrimary;
@ -678,7 +682,8 @@ namespace DualScreenDemo
if (overlayForm.InvokeRequired)
{
overlayForm.Invoke(new MethodInvoker(() => {
overlayForm.Invoke(new MethodInvoker(() =>
{
overlayForm.UpdateMarqueeText(nextSongText, OverlayForm.MarqueeStartPosition.Middle, Color.White);
}));
}
@ -691,7 +696,8 @@ namespace DualScreenDemo
// 重置跑马灯文本
if (overlayForm.InvokeRequired)
{
overlayForm.Invoke(new MethodInvoker(() => {
overlayForm.Invoke(new MethodInvoker(() =>
{
overlayForm.ResetMarqueeTextToWelcomeMessage();
}));
}
@ -931,7 +937,8 @@ namespace DualScreenDemo
/*如果當前為公播,不可以+1*/
bool isPlayingPublicList = PrimaryForm.userRequestedSongs.Count == 0 ||
(PrimaryForm.currentSongIndexInHistory >= PrimaryForm.userRequestedSongs.Count - 1 && PrimaryForm.Instance.videoPlayerForm.IsPlayingPublicSong);
if(!isPlayingPublicList){
if (!isPlayingPublicList)
{
PrimaryForm.currentSongIndexInHistory += 1;
}
Console.WriteLine("currentSongIndexInHistory : " + PrimaryForm.currentSongIndexInHistory);
@ -1817,5 +1824,67 @@ namespace DualScreenDemo
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
View 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}");
}
}