新增看門狗
This commit is contained in:
parent
39c2a39fd8
commit
4d09ed914a
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; }
|
||||
}
|
||||
}
|
@ -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;
|
||||
|
@ -3,6 +3,8 @@ using System.Runtime.InteropServices;
|
||||
using DirectShowLib;
|
||||
using DBObj;
|
||||
using OverlayFormObj;
|
||||
using DualScreenDemo.Shared;
|
||||
|
||||
namespace DualScreenDemo
|
||||
{
|
||||
public class VideoPlayerForm : Form
|
||||
@ -394,7 +396,7 @@ namespace DualScreenDemo
|
||||
}
|
||||
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
|
||||
}
|
||||
}
|
||||
@ -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;
|
||||
@ -479,7 +483,7 @@ namespace DualScreenDemo
|
||||
MessageBox.Show(String.Format("Error syncing to primary monitor: {0}", ex.Message));
|
||||
}
|
||||
}
|
||||
public void ClosePrimaryScreenPanel()
|
||||
public void ClosePrimaryScreenPanel()
|
||||
{
|
||||
try
|
||||
{
|
||||
@ -609,7 +613,7 @@ namespace DualScreenDemo
|
||||
if (File.Exists(welcomePath))
|
||||
{
|
||||
publicPlaylist.Add(new SongData(
|
||||
"0", "歡迎光臨", "", "","", welcomePath,
|
||||
"0", "歡迎光臨", "", "", "", welcomePath,
|
||||
"", "", "", "", 1
|
||||
));
|
||||
}
|
||||
@ -622,7 +626,7 @@ namespace DualScreenDemo
|
||||
{
|
||||
publicPlaylist.Add(new SongData(
|
||||
i.ToString(), $"背景音樂{i:D2}",
|
||||
"", "", "",bgmPath, "", "", "", "", 1
|
||||
"", "", "", bgmPath, "", "", "", "", 1
|
||||
));
|
||||
}
|
||||
}
|
||||
@ -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();
|
||||
}));
|
||||
}
|
||||
@ -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;
|
||||
// pathToPlay 需要調整
|
||||
var pathToPlay = File.Exists(songToPlay.SongFilePathHost1) ? songToPlay.SongFilePathHost1 : songToPlay.SongFilePathHost2;
|
||||
|
||||
// 若兩個 host 上都找不到檔案就直接結束
|
||||
if (!File.Exists(pathToPlay))
|
||||
{
|
||||
Console.WriteLine($"文件不存在:{pathToPlay}");
|
||||
return;
|
||||
}
|
||||
// 若兩個 host 上都找不到檔案就直接結束
|
||||
if (!File.Exists(pathToPlay))
|
||||
{
|
||||
Console.WriteLine($"文件不存在:{pathToPlay}");
|
||||
return;
|
||||
}
|
||||
|
||||
// 更新目前正在播放的歌曲
|
||||
currentPlayingSong = songToPlay;
|
||||
// 更新目前正在播放的歌曲
|
||||
currentPlayingSong = songToPlay;
|
||||
|
||||
// 更新畫面上顯示的下一首歌資訊
|
||||
UpdateNextSongFromPlaylist();
|
||||
// 更新畫面上顯示的下一首歌資訊
|
||||
UpdateNextSongFromPlaylist();
|
||||
|
||||
// 顯示 QRCode(可能是點歌頁用)
|
||||
overlayForm.DisplayQRCodeOnOverlay(HttpServer.randomFolderPath);
|
||||
// 顯示 QRCode(可能是點歌頁用)
|
||||
overlayForm.DisplayQRCodeOnOverlay(HttpServer.randomFolderPath);
|
||||
|
||||
// 隱藏「暫停中」標籤
|
||||
overlayForm.HidePauseLabel();
|
||||
// 隱藏「暫停中」標籤
|
||||
overlayForm.HidePauseLabel();
|
||||
|
||||
|
||||
try
|
||||
@ -931,8 +937,9 @@ namespace DualScreenDemo
|
||||
/*如果當前為公播,不可以+1*/
|
||||
bool isPlayingPublicList = PrimaryForm.userRequestedSongs.Count == 0 ||
|
||||
(PrimaryForm.currentSongIndexInHistory >= PrimaryForm.userRequestedSongs.Count - 1 && PrimaryForm.Instance.videoPlayerForm.IsPlayingPublicSong);
|
||||
if(!isPlayingPublicList){
|
||||
PrimaryForm.currentSongIndexInHistory+=1;
|
||||
if (!isPlayingPublicList)
|
||||
{
|
||||
PrimaryForm.currentSongIndexInHistory += 1;
|
||||
}
|
||||
Console.WriteLine("currentSongIndexInHistory : " + PrimaryForm.currentSongIndexInHistory);
|
||||
}
|
||||
@ -1533,7 +1540,7 @@ namespace DualScreenDemo
|
||||
return -10000;
|
||||
}
|
||||
private bool isVocalRemoved = false;
|
||||
public async void ToggleVocalRemoval()
|
||||
public async void ToggleVocalRemoval()
|
||||
{
|
||||
try
|
||||
{
|
||||
@ -1602,7 +1609,7 @@ namespace DualScreenDemo
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Console.WriteLine( ex.Message);
|
||||
Console.WriteLine(ex.Message);
|
||||
}
|
||||
}
|
||||
|
||||
@ -1649,7 +1656,7 @@ namespace DualScreenDemo
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Console.WriteLine( ex.Message);
|
||||
Console.WriteLine(ex.Message);
|
||||
}
|
||||
}
|
||||
|
||||
@ -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
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}");
|
||||
}
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user