using System.Runtime.InteropServices; using DBObj; using OverlayFormObj; using DualScreenDemo.Services; using LibVLCSharp; 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 MediaService _mediaService0 = new MediaService(); private MediaService _mediaService1 = new MediaService(); public static OverlayForm overlayForm; public bool isMuted = false; public int previousVolume = 100; public bool isPaused = false; private bool isSyncToPrimaryMonitor = false; public bool IsSyncToPrimaryMonitor { get { return isSyncToPrimaryMonitor; } set { isSyncToPrimaryMonitor = value; } } private static Screen secondMonitor; public VideoPlayerForm() { Instance = this; // this.DoubleBuffered = true; this.Load += VideoPlayerForm_Load; this.Shown += VideoPlayerForm_Shown; this.FormClosing += VideoPlayerForm_FormClosing; InitializeOverlayForm(secondMonitor); BringOverlayToFront(); HttpServer.OnDisplayBarrage += DisplayBarrageOnOverlay; MonitorMediaEvents(); } 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 { // _mediaService0.SetVideoOutput(PrimaryForm.Instance.videoPanel.Handle, PrimaryForm.Instance.videoPanel.Width, PrimaryForm.Instance.videoPanel.Height); //_mediaService1.SetVideoOutput(this.Handle, secondMonitor.Bounds.Width, secondMonitor.Bounds.Height); this.Controls.Add(PrimaryForm.Instance.videoView1); PrimaryForm.Instance.videoView1.MediaPlayer = _mediaService1.Player; PrimaryForm.Instance.videoView0.MediaPlayer = _mediaService0.Player; PlayNextSong(); } } private void VideoPlayerForm_FormClosing(object sender, FormClosingEventArgs e) { _mediaService0.Dispose(); _mediaService1.Dispose(); // 清理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() { try { // 等待初始化完成(例如播放器、串口等資源尚未就緒時) 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); } } // 更新畫面上顯示的下一首歌資訊 SongList.UpdateNextSongLabel(); // 顯示 QRCode(可能是點歌頁用) overlayForm.DisplayQRCodeOnOverlay(HttpServer.randomFolderPath); // 隱藏「暫停中」標籤 overlayForm.HidePauseLabel(); await _InitializeAndPlayMedia(songToPlay); } catch (Exception ex) { Console.WriteLine(ex.Message); } } public async Task ReplayCurrentSong() { if (Program.room.IsClose()) { return; } else { await _InitializeAndPlayMedia(SongList.Current()); } } private async Task _InitializeAndPlayMedia(SongData song) { try { if (InvokeRequired) { await InvokeAsync(() => InitializeAndPlayMedia(song)); } else { await InitializeAndPlayMedia(song); } } catch (Exception ex) { Console.WriteLine($"播放時發生錯誤: {ex.Message}"); await RetryInitializeAndPlayMedia(song); } } private async Task RetryInitializeAndPlayMedia(SongData song) { try { int hr = CoInitializeEx(IntPtr.Zero, COINIT.APARTMENTTHREADED); if (hr >= 0) { await InitializeAndPlayMedia(song); } 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) { Program.WriteLog(ex.ToString()); tcs.SetException(ex); } }); return tcs.Task; } private Task InitializeAndPlayMedia(SongData song) { try { string pathToPlay = song.getFile(); _mediaService0.LoadMedia(pathToPlay, 0); _mediaService0.Player.Media.AddOption(":no-audio"); _mediaService0.Player.AspectRatio = "8:5"; // _mediaService0.Player.Scale = 1.5f; _mediaService1.LoadMedia(pathToPlay, song.isPublicSong ? 0 : 1); if (song.isPublicSong) { PrimaryForm.Instance.originalSongButton.Click -= PrimaryForm.Instance.OriginalSongButton_Click; } else { PrimaryForm.Instance.originalSongButton.Click -= PrimaryForm.Instance.OriginalSongButton_Click; PrimaryForm.Instance.originalSongButton.Click += PrimaryForm.Instance.OriginalSongButton_Click; } isVocalRemoved = true; if (isMuted) { Mute(true); } // 音量處理 SetVolume(100 + song.getBasic().getDbChange()); Task.Run(() => Thread.Sleep(100)); Console.WriteLine(_mediaService1.GetVolume()); if (isSyncToPrimaryMonitor) SyncToPrimaryMonitor(); return Task.CompletedTask; } catch (Exception ex) { Console.WriteLine(ex.Message); Program.WriteLog(ex.ToString()); 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 () => { await Task.Delay(10000); Console.WriteLine("開始監聽媒體事件..."); while (true) { try { if (_mediaService1.IsAtEndTime()) { BeginInvoke(new Action(async () => { await PlayNextSong(); })); } } catch (Exception ex) { Console.WriteLine($"監控媒體事件時發生錯誤: {ex.Message}"); } await Task.Delay(1000); } }); } public void BringOverlayToFront() { if (overlayForm != null) { if (!overlayForm.Visible) { overlayForm.Show(); } overlayForm.BringToFront(); overlayForm.TopMost = true; } } public void Play() { _mediaService0.Play(); _mediaService1.Play(); isPaused = false; OverlayForm.MainForm.HidePauseLabel(); } public void Stop() { _mediaService0.Stop(); _mediaService1.Stop(); } public void Pause() { _mediaService0.Pause(); _mediaService1.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 bool Mute(bool isMuted) { if (isMuted) { // _mediaService0.Mute(true); _mediaService1.Mute(true); } else { // _ToggleVocalRemoval(isVocalRemoved); _mediaService1.Mute(false); } return isMuted; } public void SetVolume(int volume) { Console.WriteLine($"SetVolume: {volume}"); _mediaService0.SetVolume(volume); _mediaService1.SetVolume(volume); } public int GetVolume() { return _mediaService1.GetVolume(); } private bool isVocalRemoved = true; public void ToggleVocalRemoval() { if (isVocalRemoved) { _mediaService1.Player.Select(TrackType.Audio, "audio/2"); isVocalRemoved = false; } else { _mediaService1.Player.Select(TrackType.Audio, "audio/1,audio/3"); // _mediaService1.Player.Select(TrackType.Audio, "audio/3"); isVocalRemoved = true; } // isVocalRemoved = _ToggleVocalRemoval(!isVocalRemoved); OverlayForm.MainForm.ShowTopRightLabelTime(isVocalRemoved ? "無人聲" : "有人聲"); } } }