新增看門狗

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

View File

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