using System.IO; // For StreamWriter using System.Runtime.InteropServices; using System.Windows.Media; using DirectShowLib; using DBObj; using OverlayFormObj; using DualScreenDemo.Services; namespace DualScreenDemo { public class VideoPlayerForm : Form { #region 防止閃屏 protected override CreateParams CreateParams { get { CreateParams cp = base.CreateParams; cp.ExStyle |= 0x02000000; return cp; } } #endregion // 单例实例 public static VideoPlayerForm Instance { get; private set; } // 导入user32.dll API [DllImport("user32.dll")] static extern bool SetWindowPos(IntPtr hWnd, IntPtr hWndInsertAfter, int X, int Y, int cx, int cy, uint uFlags); [DllImport("user32.dll", SetLastError = true)] static extern IntPtr GetWindowLong(IntPtr hWnd, int nIndex); [DllImport("user32.dll")] static extern int SetWindowLong(IntPtr hWnd, int nIndex, IntPtr dwNewLong); // Windows API 函數 [DllImport("user32.dll")] static extern IntPtr GetDesktopWindow(); [DllImport("user32.dll")] static extern IntPtr MonitorFromWindow(IntPtr hwnd, uint dwFlags); [DllImport("user32.dll")] static extern bool GetMonitorInfo(IntPtr hMonitor, ref MONITORINFO lpmi); // MONITORINFO 結構 [StructLayout(LayoutKind.Sequential)] struct MONITORINFO { public int cbSize; public RECT rcMonitor; public RECT rcWork; public uint dwFlags; } // RECT 結構 [StructLayout(LayoutKind.Sequential)] struct RECT { public int Left; public int Top; public int Right; public int Bottom; } private const int GWL_EXSTYLE = -20; private const int WS_EX_TOPMOST = 0x00000008; private const uint SWP_NOZORDER = 0x0004; private MediaServicePrimary primary = new MediaServicePrimary(); private MediaService secondary = new MediaService(); public static OverlayForm overlayForm; public bool isMuted = false; public int previousVolume = 0; public bool isPaused = false; private bool isSyncToPrimaryMonitor = false; public bool IsSyncToPrimaryMonitor { get { return isSyncToPrimaryMonitor; } set { isSyncToPrimaryMonitor = value; } } private static Screen secondMonitor; private int newWidth; private int newHeight; public VideoPlayerForm() { Lold(); Instance = this; // this.DoubleBuffered = true; this.Load += VideoPlayerForm_Load; this.Shown += VideoPlayerForm_Shown; this.FormClosing += VideoPlayerForm_FormClosing; InitializeOverlayForm(secondMonitor); BringOverlayToFront(); HttpServer.OnDisplayBarrage += DisplayBarrageOnOverlay; MonitorMediaEvents(); } void Lold(){ int designWidth = 1620; int designHeight = 1080; int actualWidth = PrimaryForm.Instance.primaryScreenPanel.Width; int actualHeight = PrimaryForm.Instance.primaryScreenPanel.Height; // 等比例縮放(不會超出,fit) float scaleX = (float)actualWidth / designWidth; float scaleY = (float)actualHeight / designHeight; float scale = Math.Min(scaleX, scaleY); // 計算縮放後的尺寸(但起點仍是 0,0) newWidth = (int)(designWidth * scale); newHeight = (int)(designHeight * scale); // trash location with trash flexible of screen size. if (actualWidth == 1024) { newWidth = (int)(newWidth * 0.84f); } else if (actualWidth == 1440) { newWidth = (int)(newWidth * 0.9f); } } private void VideoPlayerForm_Load(object sender, EventArgs e) { secondMonitor = ScreenHelper.GetSecondMonitor(); if (secondMonitor != null) { this.FormBorderStyle = FormBorderStyle.None; // 设置窗体没有边框 this.StartPosition = FormStartPosition.Manual; this.Location = secondMonitor.Bounds.Location; this.Size = secondMonitor.Bounds.Size; // this.DoubleBuffered = true; } Screen screen = Screen.FromHandle(this.Handle); } private void VideoPlayerForm_Shown(object sender, EventArgs e) { int hr = CoInitializeEx(IntPtr.Zero, COINIT.APARTMENTTHREADED); if (hr < 0) { Console.WriteLine("Failed to initialize COM library."); return; } else { PlayNextSong(); } } private void VideoPlayerForm_FormClosing(object sender, FormClosingEventArgs e) { primary.VideoPlayerFormClosing(); secondary.VideoPlayerFormClosing(); // 清理COM CoUninitialize(); } // COM API函数声明 [DllImport("ole32.dll")] private static extern int CoInitializeEx(IntPtr pvReserved, COINIT dwCoInit); [DllImport("ole32.dll")] private static extern void CoUninitialize(); // CoInitializeEx() 可以选择的参数 private enum COINIT : int { APARTMENTTHREADED = 0x2, MULTITHREADED = 0x0 } // 同步畫面事件 public void SyncToPrimaryMonitor() { PrimaryForm.Instance.primaryScreenPanel.Visible = true; PrimaryForm.Instance.primaryScreenPanel.BringToFront(); PrimaryForm.Instance.syncServiceBellButton.Visible = true; PrimaryForm.Instance.syncServiceBellButton.BringToFront(); PrimaryForm.Instance.syncCutSongButton.Visible = true; PrimaryForm.Instance.syncCutSongButton.BringToFront(); PrimaryForm.Instance.syncReplayButton.Visible = true; PrimaryForm.Instance.syncReplayButton.BringToFront(); PrimaryForm.Instance.syncOriginalSongButton.Visible = true; PrimaryForm.Instance.syncOriginalSongButton.BringToFront(); PrimaryForm.Instance.syncMuteButton.Visible = true; PrimaryForm.Instance.syncMuteButton.BringToFront(); if (isPaused) { PrimaryForm.Instance.syncPlayButton.Visible = true; PrimaryForm.Instance.syncPlayButton.BringToFront(); } else { PrimaryForm.Instance.syncPauseButton.Visible = true; PrimaryForm.Instance.syncPauseButton.BringToFront(); } PrimaryForm.Instance.syncVolumeUpButton.Visible = true; PrimaryForm.Instance.syncVolumeUpButton.BringToFront(); PrimaryForm.Instance.syncVolumeDownButton.Visible = true; PrimaryForm.Instance.syncVolumeDownButton.BringToFront(); PrimaryForm.Instance.syncMicUpButton.Visible = true; PrimaryForm.Instance.syncMicUpButton.BringToFront(); PrimaryForm.Instance.syncMicDownButton.Visible = true; PrimaryForm.Instance.syncMicDownButton.BringToFront(); PrimaryForm.Instance.syncCloseButton.Visible = true; PrimaryForm.Instance.syncCloseButton.BringToFront(); } public void ClosePrimaryScreenPanel() { try { PrimaryForm.Instance.primaryScreenPanel.Visible = false; PrimaryForm.Instance.syncServiceBellButton.Visible = false; PrimaryForm.Instance.syncCutSongButton.Visible = false; PrimaryForm.Instance.syncReplayButton.Visible = false; PrimaryForm.Instance.syncOriginalSongButton.Visible = false; PrimaryForm.Instance.syncMuteButton.Visible = false; PrimaryForm.Instance.syncPauseButton.Visible = false; PrimaryForm.Instance.syncPlayButton.Visible = false; PrimaryForm.Instance.syncVolumeUpButton.Visible = false; PrimaryForm.Instance.syncVolumeDownButton.Visible = false; PrimaryForm.Instance.syncMicUpButton.Visible = false; PrimaryForm.Instance.syncMicDownButton.Visible = false; PrimaryForm.Instance.syncCloseButton.Visible = false; IsSyncToPrimaryMonitor = false; } catch (Exception ex) { Console.WriteLine(String.Format("Error closing primary screen panel: {0}", ex.Message)); MessageBox.Show(String.Format("Error closing primary screen panel: {0}", ex.Message)); } } [DllImport("gdi32.dll", ExactSpelling = true)] public static extern bool BitBlt(IntPtr hdcDest, int nXDest, int nYDest, int nWidth, int nHeight, IntPtr hdcSrc, int nXSrc, int nYSrc, TernaryRasterOperations dwRop); public enum TernaryRasterOperations : uint { SRCCOPY = 0x00CC0020, } private void DisplayBarrageOnOverlay(string text) { if (overlayForm.InvokeRequired) overlayForm.Invoke(new System.Action(() => overlayForm.DisplayBarrage(text))); else overlayForm.DisplayBarrage(text); } public async Task PlayNextSong() { // 等待初始化完成(例如播放器、串口等資源尚未就緒時) Console.WriteLine("開始播放下一首歌曲..."); var songToPlay = SongList.Next(); if (!songToPlay.isPublicSong) { // 若是使用者點播模式,先送出升Key的串口指令 if (SerialPortManager.mySerialPort != null && SerialPortManager.mySerialPort.IsOpen) { byte[] commandBytesIncreasePitch1 = new byte[] { 0xA2, 0x7F, 0xA4 }; SerialPortManager.mySerialPort.Write(commandBytesIncreasePitch1, 0, commandBytesIncreasePitch1.Length); } } // pathToPlay 需要調整 var pathToPlay = songToPlay.getFile(); // 更新畫面上顯示的下一首歌資訊 SongList.UpdateNextSongLabel(); // 顯示 QRCode(可能是點歌頁用) overlayForm.DisplayQRCodeOnOverlay(HttpServer.randomFolderPath); // 隱藏「暫停中」標籤 overlayForm.HidePauseLabel(); await _InitializeAndPlayMedia(pathToPlay); } public async Task ReplayCurrentSong() { var songToPlay = SongList.Current(); var pathToPlay = songToPlay.getFile(); // UpdateMarqueeTextForCurrentSong(songToPlay); await _InitializeAndPlayMedia(pathToPlay); } private async Task _InitializeAndPlayMedia(string pathToPlay) { try { if (InvokeRequired) { await InvokeAsync(() => InitializeAndPlayMedia(pathToPlay)); } else { await InitializeAndPlayMedia(pathToPlay); } } catch (Exception ex) { Console.WriteLine($"播放時發生錯誤: {ex.Message}"); await RetryInitializeAndPlayMedia(pathToPlay); } } private async Task RetryInitializeAndPlayMedia(string path) { try { int hr = CoInitializeEx(IntPtr.Zero, COINIT.APARTMENTTHREADED); if (hr >= 0) { await InitializeAndPlayMedia(path); } else { Console.WriteLine("CoInitializeEx 失敗,無法重新初始化 COM。"); } } catch (Exception retryEx) { Console.WriteLine($"重試播放時發生錯誤: {retryEx.Message}"); } } // 通用的 async invoke 方法(避免重複寫) private Task InvokeAsync(Func func) { var tcs = new TaskCompletionSource(); BeginInvoke(async () => { try { await func(); tcs.SetResult(true); } catch (Exception ex) { tcs.SetException(ex); } }); return tcs.Task; } private Task InitializeAndPlayMedia(string pathToPlay) { // 渲染媒體文件 primary.RenderMediaFile(pathToPlay,PrimaryForm.Instance.primaryScreenPanel.Handle,newWidth,newHeight); secondary.RenderMediaFile(pathToPlay,this.Handle,secondMonitor.Bounds.Width,secondMonitor.Bounds.Height); // 音量處理 SetVolume(isMuted ? -10000 : previousVolume); // 開始播放 primary.Run(); secondary.Run(); if (isSyncToPrimaryMonitor) SyncToPrimaryMonitor(); return Task.CompletedTask; } private void InitializeOverlayForm(Screen secondaryScreen) { overlayForm = new OverlayForm(); Screen secondMonitor = ScreenHelper.GetSecondMonitor(); if (secondMonitor != null) { overlayForm.Location = secondMonitor.WorkingArea.Location; overlayForm.StartPosition = FormStartPosition.Manual; overlayForm.Size = new Size(secondMonitor.WorkingArea.Width, secondMonitor.WorkingArea.Height); } overlayForm.ShowInTaskbar = false; overlayForm.Owner = this; overlayForm.Show(); this.Focus(); } public void MonitorMediaEvents() { Task.Run(async () => { Console.WriteLine("開始監聽媒體事件..."); while (true) { try { bool isAtEnd = secondary.isAtEnd() ; if (isAtEnd && !isPaused) { BeginInvoke(new Action(async () => { await PlayNextSong(); })); } } catch (Exception ex) { Console.WriteLine($"監控媒體事件時發生錯誤: {ex.Message}"); await Task.Delay(1000); } await Task.Delay(1000); } }); } public void BringOverlayToFront() { if (overlayForm != null) { if (!overlayForm.Visible) { overlayForm.Show(); } overlayForm.BringToFront(); overlayForm.TopMost = true; } } public void Play() { primary.Run(); secondary.Run(); isPaused = false; OverlayForm.MainForm.HidePauseLabel(); } public void Stop() { primary.Stop(); secondary.Stop(); } public void Pause() { primary.Pause(); secondary.Pause(); isPaused = true; OverlayForm.MainForm.ShowPauseLabel(); } public void PauseOrResumeSong() { if (isPaused) { Play(); PrimaryForm.Instance.pauseButton.Visible = true; PrimaryForm.Instance.playButton.Visible = false; PrimaryForm.Instance.syncPauseButton.Visible = true; PrimaryForm.Instance.syncPlayButton.Visible = false; } else { Pause(); PrimaryForm.Instance.pauseButton.Visible = false; PrimaryForm.Instance.playButton.Visible = true; PrimaryForm.Instance.syncPauseButton.Visible = false; PrimaryForm.Instance.syncPlayButton.Visible = true; OverlayForm.MainForm.ShowPauseLabel(); } } private void UpdateSyncButtons() { if (isPaused) { PrimaryForm.Instance.syncPlayButton.Visible = true; PrimaryForm.Instance.syncPauseButton.Visible = false; } else { PrimaryForm.Instance.syncPlayButton.Visible = false; PrimaryForm.Instance.syncPauseButton.Visible = true; } } public void SetVolume(int volume) { secondary.SetVolume(volume); } public int GetVolume() { return secondary.GetVolume(); } public void ToggleVocalRemoval() { OverlayForm.MainForm.ShowTopRightLabelTime(secondary.ToggleVocalRemoval() ? "無人聲" : "有人聲"); } } }