using System.IO; // For StreamWriter using System.Runtime.InteropServices; using DirectShowLib; using DBObj; using OverlayFormObj; 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 IGraphBuilder graphBuilderPrimary; private IGraphBuilder graphBuilderSecondary; private IMediaControl mediaControlPrimary; private IMediaControl mediaControlSecondary; private static IBaseFilter videoRendererSecondary; private static IBaseFilter videoRendererPrimary; private IBaseFilter lavSplitterPrimary; private IBaseFilter lavSplitterSecondary; private IBaseFilter lavVideoDecoderPrimary; private IBaseFilter lavVideoDecoderSecondary; private static IBaseFilter lavAudioDecoderSecondary; private IPin outputPinSecondary; private static IBaseFilter audioRenderer; private IVideoWindow videoWindowSecondary; private IVideoWindow videoWindowPrimary; private IMediaEventEx mediaEventExPrimary; private IMediaEventEx mediaEventExSecondary; //private int videoWidth; //private int videoHeight; private static bool isInitializationComplete = false; public static OverlayForm overlayForm; public bool isMuted = false; public int previousVolume = -1000; 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; } CheckMonitor(); } 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; } InitializeGraphBuilderPrimary(); InitializeGraphBuilderSecondary(); } private void VideoPlayerForm_FormClosing(object sender, FormClosingEventArgs e) { if (videoWindowPrimary != null) { videoWindowPrimary.put_Visible(OABool.False); videoWindowPrimary.put_Owner(IntPtr.Zero); Marshal.ReleaseComObject(videoWindowPrimary); videoWindowPrimary = null; } if (videoWindowSecondary != null) { videoWindowSecondary.put_Visible(OABool.False); videoWindowSecondary.put_Owner(IntPtr.Zero); Marshal.ReleaseComObject(videoWindowSecondary); videoWindowSecondary = null; } // 清理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 } private void InitializeGraphBuilderPrimary() { graphBuilderPrimary = (IGraphBuilder)new FilterGraph(); if (graphBuilderPrimary == null) { Console.WriteLine("Failed to create FilterGraph for primary monitor."); throw new Exception("Failed to create FilterGraph for primary monitor."); } try { lavSplitterPrimary = AddFilterByClsid(graphBuilderPrimary, "LAV Splitter", Clsid.LAVSplitter); lavVideoDecoderPrimary = AddFilterByClsid(graphBuilderPrimary, "LAV Video Decoder", Clsid.LAVVideoDecoder); videoRendererPrimary = AddFilterByClsid(graphBuilderPrimary, "Primary Video Renderer", Clsid.VideoRenderer); int hr = graphBuilderPrimary.AddFilter(videoRendererPrimary, "Primary Video Renderer"); DsError.ThrowExceptionForHR(hr); mediaControlPrimary = (IMediaControl)graphBuilderPrimary; if (mediaControlPrimary == null) { Console.WriteLine("Failed to get Media Control for primary monitor."); return; } mediaEventExPrimary = (IMediaEventEx)graphBuilderPrimary; if (mediaEventExPrimary == null) { Console.WriteLine("Failed to get Media Event Ex for primary monitor."); return; } } catch (Exception ex) { Console.WriteLine("Error initializing graph builder for primary monitor: " + ex.Message); } } private void InitializeGraphBuilderSecondary() { graphBuilderSecondary = (IGraphBuilder)new FilterGraph(); if (graphBuilderSecondary == null) { Console.WriteLine("Failed to create FilterGraph"); throw new Exception("Failed to create FilterGraph"); } try { lavSplitterSecondary = AddFilterByClsid(graphBuilderSecondary, "LAV Splitter", Clsid.LAVSplitter); lavVideoDecoderSecondary = AddFilterByClsid(graphBuilderSecondary, "LAV Video Decoder", Clsid.LAVVideoDecoder); lavAudioDecoderSecondary = AddFilterByClsid(graphBuilderSecondary, "LAV Audio Decoder", Clsid.LAVAudioDecoder); outputPinSecondary = FindPin(lavAudioDecoderSecondary, "Output"); videoRendererSecondary = AddFilterByClsid(graphBuilderSecondary, "Secondary Video Renderer", Clsid.VideoRenderer); if (videoRendererSecondary == null) { Console.WriteLine("Failed to initialize Secondary Video Renderer."); return; } int hr = graphBuilderSecondary.AddFilter(videoRendererSecondary, "Secondary Video Renderer"); DsError.ThrowExceptionForHR(hr); var clsidAudioRenderer = new Guid("79376820-07D0-11CF-A24D-0020AFD79767"); // CLSID for DirectSound Renderer audioRenderer = (IBaseFilter)Activator.CreateInstance(Type.GetTypeFromCLSID(clsidAudioRenderer)); hr = graphBuilderSecondary.AddFilter(audioRenderer, "Default DirectSound Device"); DsError.ThrowExceptionForHR(hr); mediaControlSecondary = (IMediaControl)graphBuilderSecondary; if (mediaControlSecondary == null) { Console.WriteLine("Failed to get Media Control"); return; } mediaEventExSecondary = (IMediaEventEx)graphBuilderSecondary; if (mediaEventExSecondary == null) { Console.WriteLine("Failed to get Media Event Ex"); return; } isInitializationComplete = true; } catch (Exception ex) { Console.WriteLine("Error initializing graph builder with second monitor: " + ex.Message); } } private int ConnectFilters(IGraphBuilder graphBuilder, IBaseFilter sourceFilter, string sourcePinName, IBaseFilter destFilter, string destPinName) { IPin outPin = FindPin(sourceFilter, sourcePinName); IPin inPin = FindPin(destFilter, destPinName); if (outPin == null || inPin == null) { Console.WriteLine(String.Format("Cannot find pins: {0} or {1}", sourcePinName, destPinName)); return -1; } int hr = graphBuilder.Connect(outPin, inPin); return hr; } private IPin FindPin(IBaseFilter filter, string pinName) { IEnumPins enumPins; IPin[] pins = new IPin[1]; filter.EnumPins(out enumPins); enumPins.Reset(); while (enumPins.Next(1, pins, IntPtr.Zero) == 0) { PinInfo pinInfo; pins[0].QueryPinInfo(out pinInfo); Console.WriteLine(pinInfo); if (pinInfo.name == pinName) { return pins[0]; } } return null; } private void CheckMonitor() { Screen screen = Screen.FromHandle(this.Handle); } private static IBaseFilter AddFilterByClsid(IGraphBuilder graphBuilder, string name, Guid clsid) { try { // 获取 CLSID 对应的类型 Type filterType = Type.GetTypeFromCLSID(clsid); //Console.WriteLine($"Attempting to create filter of type: {filterType.FullName}"); // 创建实例 object filterObject = Activator.CreateInstance(filterType); // 尝试转换为 IBaseFilter IBaseFilter filter = filterObject as IBaseFilter; if (filter == null) { // 如果转换失败,使用 IUnknown 获取并转换为 IBaseFilter IntPtr comObjectPointer = Marshal.GetIUnknownForObject(filterObject); filter = (IBaseFilter)Marshal.GetObjectForIUnknown(comObjectPointer); // Console.WriteLine($"Successfully converted COM object to IBaseFilter via IUnknown."); } else { // Console.WriteLine($"Successfully created IBaseFilter directly."); } // 添加过滤器到图形构建器 int hr = graphBuilder.AddFilter(filter, name); if (hr != 0) { // Console.WriteLine($"Failed to add filter {name} with CLSID {clsid}, HRESULT: {hr}"); } DsError.ThrowExceptionForHR(hr); // Console.WriteLine($"Successfully added filter {name} with CLSID {clsid}"); return filter; } catch (Exception ex) { Console.WriteLine($"Exception in AddFilterByClsid: {ex.Message}"); throw; // Rethrow the exception to handle it further up the call stack } } // 同步畫面事件 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(); try { if (videoRendererPrimary == null) { // Console.WriteLine("VMR9 is not initialized."); return; } 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) int newWidth = (int)(designWidth * scale); int 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); } videoWindowPrimary = (IVideoWindow)videoRendererPrimary; videoWindowPrimary.put_Owner(PrimaryForm.Instance.primaryScreenPanel.Handle); videoWindowPrimary.put_WindowStyle(WindowStyle.Child | WindowStyle.ClipChildren); // ⬅ 左上角對齊(固定從 0,0 開始) videoWindowPrimary.SetWindowPosition(0, 0, newWidth, newHeight); videoWindowPrimary.put_Visible(OABool.True); // Console.WriteLine("Video window configured successfully."); } catch (Exception ex) { Console.WriteLine(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() { 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; if (videoWindowPrimary != null) { videoWindowPrimary.put_Owner(PrimaryForm.Instance.primaryScreenPanel.Handle); // 绑定到主屏幕的特定区域 videoWindowPrimary.put_WindowStyle(WindowStyle.Child | WindowStyle.ClipChildren | WindowStyle.ClipSiblings); videoWindowPrimary.put_Visible(OABool.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() { // 等待初始化完成(例如播放器、串口等資源尚未就緒時) while (!isInitializationComplete) { await Task.Delay(100); } StopAndReleaseResources(); await Task.Delay(100); 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(); try { // 確保在UI線程上執行COM對象操作 if (this.InvokeRequired) this.Invoke(new Action(async () => { await InitializeAndPlayMedia(pathToPlay); })); else await InitializeAndPlayMedia(pathToPlay); } catch (Exception ex) { Console.WriteLine($"播放時發生錯誤: {ex.Message}"); // 嘗試重新初始化並重播 try { await Task.Delay(100); StopAndReleaseResources(); await Task.Delay(100); // 重新初始化 COM int hr = CoInitializeEx(IntPtr.Zero, COINIT.APARTMENTTHREADED); if (hr >= 0) { InitializeGraphBuilderPrimary(); InitializeGraphBuilderSecondary(); await InitializeAndPlayMedia(pathToPlay); } } catch (Exception retryEx) { Console.WriteLine($"重試播放時發生錯誤: {retryEx.Message}"); } } } private async Task InitializeAndPlayMedia(string pathToPlay) { if (videoWindowPrimary != null) videoWindowPrimary.put_Visible(OABool.False); if (videoWindowSecondary != null) videoWindowSecondary.put_Visible(OABool.False); // 清理並初始化 DirectShow 圖表 RemoveAllFilters(graphBuilderPrimary); RemoveAllFilters(graphBuilderSecondary); InitializeGraphBuilderPrimary(); InitializeGraphBuilderSecondary(); // 渲染媒體文件 RenderMediaFilePrimary(pathToPlay); RenderMediaFileSecondary(pathToPlay); // 綁定視頻窗口到副屏幕 videoWindowSecondary = (IVideoWindow)videoRendererSecondary; if (videoWindowSecondary != null) { videoWindowSecondary.put_Owner(this.Handle); videoWindowSecondary.put_WindowStyle(WindowStyle.Child | WindowStyle.ClipChildren | WindowStyle.ClipSiblings); videoWindowSecondary.SetWindowPosition(0, 0, secondMonitor.Bounds.Width, secondMonitor.Bounds.Height); await Task.Delay(100); // 給予視窗一些時間進行設置 videoWindowSecondary.put_Visible(OABool.True); } // 音量處理 if (isMuted) { SetVolume(-10000); } // 開始播放 if (mediaControlPrimary != null) mediaControlPrimary.Run(); if (mediaControlSecondary != null) mediaControlSecondary.Run(); if (isSyncToPrimaryMonitor) SyncToPrimaryMonitor(); } public void ReplayCurrentSong() { var songToPlay = SongList.Current(); var pathToPlay = songToPlay.getFile(); // UpdateMarqueeTextForCurrentSong(songToPlay); try { if (mediaControlPrimary != null) mediaControlPrimary.Stop(); if (mediaControlSecondary != null) mediaControlSecondary.Stop(); if (videoWindowPrimary != null) videoWindowPrimary.put_Visible(OABool.False); // 隐藏主屏幕窗口,避免干扰 if (videoWindowSecondary != null) videoWindowSecondary.put_Visible(OABool.False); // 隐藏副屏幕窗口,避免闪烁 // 清理并初始化 DirectShow 图表 RemoveAllFilters(graphBuilderPrimary); RemoveAllFilters(graphBuilderSecondary); InitializeGraphBuilderPrimary(); InitializeGraphBuilderSecondary(); // 渲染媒体文件 RenderMediaFilePrimary(pathToPlay); RenderMediaFileSecondary(pathToPlay); // 绑定视频窗口到副屏幕 videoWindowSecondary = videoRendererSecondary as IVideoWindow; if (videoWindowSecondary != null) { videoWindowSecondary.put_Owner(this.Handle); // 副屏幕窗口句柄 videoWindowSecondary.put_WindowStyle(WindowStyle.Child | WindowStyle.ClipChildren | WindowStyle.ClipSiblings); videoWindowSecondary.SetWindowPosition(0, 0, secondMonitor.Bounds.Width, secondMonitor.Bounds.Height); videoWindowSecondary.put_Visible(OABool.True); // 显示窗口 } // 音量处理 if (isMuted) SetVolume(-10000); // 静音 // 开始播放 mediaControlPrimary?.Run(); mediaControlSecondary?.Run(); if (isSyncToPrimaryMonitor) SyncToPrimaryMonitor(); } catch (Exception ex) { Console.WriteLine(String.Format("Error replaying song: {0}", ex.Message)); MessageBox.Show(String.Format("Error replaying song: {0}", ex.Message)); } } public void StopAndReleaseResources() { try { if (mediaControlPrimary != null) { mediaControlPrimary.Stop(); Marshal.ReleaseComObject(mediaControlPrimary); mediaControlPrimary = null; } if (mediaControlSecondary != null) { mediaControlSecondary.Stop(); Marshal.ReleaseComObject(mediaControlSecondary); mediaControlSecondary = null; } // 释放其他资源 if (lavSplitterPrimary != null) { Marshal.ReleaseComObject(lavSplitterPrimary); lavSplitterPrimary = null; } if (lavSplitterSecondary != null) { Marshal.ReleaseComObject(lavSplitterSecondary); lavSplitterSecondary = null; } if (lavVideoDecoderPrimary != null) { Marshal.ReleaseComObject(lavVideoDecoderPrimary); lavVideoDecoderPrimary = null; } if (lavVideoDecoderSecondary != null) { Marshal.ReleaseComObject(lavVideoDecoderSecondary); lavVideoDecoderSecondary = null; } if (lavAudioDecoderSecondary != null) { Marshal.ReleaseComObject(lavAudioDecoderSecondary); lavAudioDecoderSecondary = null; } if (outputPinSecondary != null) { Marshal.ReleaseComObject(outputPinSecondary); outputPinSecondary = null; } // 强制进行垃圾回收 GC.Collect(); GC.WaitForPendingFinalizers(); } catch (Exception ex) { Console.WriteLine($"釋放資源時發生錯誤: {ex.Message}"); } } public void RenderMediaFilePrimary(string filePath) { try { int hr; IBaseFilter sourceFilter; hr = graphBuilderPrimary.AddSourceFilter(filePath, "Source", out sourceFilter); DsError.ThrowExceptionForHR(hr); videoWindowPrimary = (IVideoWindow)videoRendererPrimary; videoWindowPrimary.put_Visible(OABool.False); hr = ConnectFilters(graphBuilderPrimary, sourceFilter, "Output", lavSplitterPrimary, "Input"); DsError.ThrowExceptionForHR(hr); hr = ConnectFilters(graphBuilderPrimary, lavSplitterPrimary, "Video", lavVideoDecoderPrimary, "Input"); DsError.ThrowExceptionForHR(hr); hr = ConnectFilters(graphBuilderPrimary, lavVideoDecoderPrimary, "Output", videoRendererPrimary, "VMR Input0"); DsError.ThrowExceptionForHR(hr); videoWindowPrimary = (IVideoWindow)videoRendererPrimary; videoWindowPrimary.put_Owner(PrimaryForm.Instance.primaryScreenPanel.Handle); // 设置为 primaryScreenPanel 的句柄 videoWindowPrimary.put_WindowStyle(WindowStyle.Child | WindowStyle.ClipChildren); videoWindowPrimary.SetWindowPosition(0, 0, 1500, 1000); // 调整视频窗口大小以填满黑色区域 //Task.Delay(100).Wait(); videoWindowPrimary.put_Visible(OABool.True); SaveGraphFile(graphBuilderPrimary, "primary_graph.grf"); Console.WriteLine("主檔案 " + ((hr == 0) ? "成功" : "失敗")); } catch (Exception ex) { Console.WriteLine("主檔案失敗2: " + ex.Message); } } public void RenderMediaFileSecondary(string filePath) { int hr = graphBuilderSecondary.RenderFile(filePath, null); DsError.ThrowExceptionForHR(hr); SaveGraphFile(graphBuilderSecondary, "secondary_graph.grf"); if (hr == 0) { Console.WriteLine("Secondary File rendered successfully."); SetAudioTrackTo(1); } else { Console.WriteLine("Failed to render secondary file."); } } public static void SaveGraphFile(IGraphBuilder graph, string filename) { var writer = new StreamWriter(filename); IFilterGraph2 graph2 = graph as IFilterGraph2; if (graph2 != null) { IEnumFilters enumFilters; graph2.EnumFilters(out enumFilters); enumFilters.Reset(); IBaseFilter[] filters = new IBaseFilter[1]; while (enumFilters.Next(1, filters, IntPtr.Zero) == 0) { FilterInfo filterInfo; filters[0].QueryFilterInfo(out filterInfo); writer.WriteLine("Filter: " + filterInfo.achName); IEnumPins enumPins; filters[0].EnumPins(out enumPins); enumPins.Reset(); IPin[] pins = new IPin[1]; while (enumPins.Next(1, pins, IntPtr.Zero) == 0) { PinInfo pinInfo; pins[0].QueryPinInfo(out pinInfo); writer.WriteLine(" Pin: " + pinInfo.name); Marshal.ReleaseComObject(pins[0]); } Marshal.ReleaseComObject(enumPins); Marshal.ReleaseComObject(filters[0]); } Marshal.ReleaseComObject(enumFilters); } writer.Close(); } private static void RemoveAllFilters(IGraphBuilder graph) { IEnumFilters enumFilters; graph.EnumFilters(out enumFilters); IBaseFilter[] filters = new IBaseFilter[1]; while (enumFilters.Next(1, filters, IntPtr.Zero) == 0) { graph.RemoveFilter(filters[0]); Marshal.ReleaseComObject(filters[0]); } Marshal.ReleaseComObject(enumFilters); } 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(); } private bool isPlayingNext = false; public void MonitorMediaEvents() { Console.WriteLine("開始監聽媒體事件..."); Task.Run(async () => { while (true) { try { if (mediaControlSecondary == null) { await Task.Delay(500); continue; } // 只檢查播放進度 IMediaSeeking mediaSeekingSecondary = graphBuilderSecondary as IMediaSeeking; if (mediaSeekingSecondary != null && !isPlayingNext) { long currentPosition = 0; long duration = 0; if (mediaSeekingSecondary.GetCurrentPosition(out currentPosition) >= 0 && mediaSeekingSecondary.GetDuration(out duration) >= 0) { double currentSeconds = currentPosition / 10000000.0; double durationSeconds = duration / 10000000.0; // 添加更严格的结束条件判断 bool isAtEnd = durationSeconds > 0 && currentSeconds > 0 && Math.Abs(currentSeconds - durationSeconds) < 0.1 && // 确保真的到了结尾 !isPaused; if (isAtEnd && !isPlayingNext) { Console.WriteLine($"檢測到歌曲結束 - 當前位置: {currentSeconds:F2}秒, 總時長: {durationSeconds:F2}秒"); BeginInvoke(new Action(async () => { StopAndReleaseResources(); await Task.Delay(100); // 给予足够的时间释放资源 await PlayNextSong(); })); } } } } catch (Exception ex) { Console.WriteLine($"監控媒體事件時發生錯誤: {ex.Message}"); isPlayingNext = false; await Task.Delay(1000); } await Task.Delay(500); } }); } public void BringOverlayToFront() { if (overlayForm != null) { if (!overlayForm.Visible) { overlayForm.Show(); } overlayForm.BringToFront(); overlayForm.TopMost = true; } } public void Play() { if (mediaControlPrimary != null) mediaControlPrimary.Run(); if (mediaControlSecondary != null) mediaControlSecondary.Run(); isPaused = false; OverlayForm.MainForm.HidePauseLabel(); } public void Stop() { if (mediaControlPrimary != null) mediaControlPrimary.Stop(); if (mediaControlSecondary != null) mediaControlSecondary.Stop(); } public void Pause() { if (mediaControlPrimary != null) mediaControlPrimary.Pause(); if (mediaControlSecondary != null) mediaControlSecondary.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) { if (audioRenderer != null) { IBasicAudio basicAudio = audioRenderer as IBasicAudio; if (basicAudio != null) { basicAudio.put_Volume(volume); } } } public int GetVolume() { if (audioRenderer != null) { IBasicAudio basicAudio = audioRenderer as IBasicAudio; if (basicAudio != null) { int volume; basicAudio.get_Volume(out volume); return volume; } } return -10000; } private bool isVocalRemoved = false; public void ToggleVocalRemoval() { try { IAMStreamSelect streamSelect = lavSplitterSecondary as IAMStreamSelect; if (streamSelect != null) { int trackCount; if (streamSelect.Count(out trackCount) == 0 && trackCount > 0) { int currentTrackIndex = -1; int audioTrack1 = -1; int audioTrack2 = -1; for (int i = 0; i < trackCount; i++) { // 獲取音軌信息 AMMediaType mediaType; AMStreamSelectInfoFlags flags; int lcid, dwGroup; string name; object pObject, pUnk; streamSelect.Info(i, out mediaType, out flags, out lcid, out dwGroup, out name, out pObject, out pUnk); if (mediaType.majorType == MediaType.Audio) { if (audioTrack1 == -1) { audioTrack1 = i; } else if (audioTrack2 == -1) { audioTrack2 = i; } if ((flags & AMStreamSelectInfoFlags.Enabled) != 0) { currentTrackIndex = i; } } DsUtils.FreeAMMediaType(mediaType); } // 切換音軌 if (currentTrackIndex == audioTrack1 && audioTrack2 != -1) { streamSelect.Enable(audioTrack2, AMStreamSelectEnableFlags.Enable); isVocalRemoved = true; } else if (currentTrackIndex == audioTrack2 && audioTrack1 != -1) { streamSelect.Enable(audioTrack1, AMStreamSelectEnableFlags.Enable); isVocalRemoved = false; } //OverlayForm.MainForm.ShowOriginalSongLabel(); string labelText = isVocalRemoved ? "無人聲" : "有人聲"; // 显示标签 // 修改成新標籤 OverlayForm.MainForm.ShowTopRightLabel(labelText); //await Task.Delay(3000); // 隐藏标签 //OverlayForm.MainForm.HideOriginalSongLabel(); } } } catch (Exception ex) { Console.WriteLine(ex.Message); } } public void SetAudioTrackTo(int trackIndex) { try { IAMStreamSelect streamSelect = lavSplitterSecondary as IAMStreamSelect; if (streamSelect != null) { int trackCount; if (streamSelect.Count(out trackCount) == 0 && trackCount > 0) { int audioTrackIndex = -1; for (int i = 0; i < trackCount; i++) { AMMediaType mediaType; AMStreamSelectInfoFlags flags; int lcid, dwGroup; string name; object pObject, pUnk; streamSelect.Info(i, out mediaType, out flags, out lcid, out dwGroup, out name, out pObject, out pUnk); if (mediaType.majorType == MediaType.Audio) { audioTrackIndex++; if (audioTrackIndex == trackIndex) { streamSelect.Enable(i, AMStreamSelectEnableFlags.Enable); } else { streamSelect.Enable(i, AMStreamSelectEnableFlags.DisableAll); } } DsUtils.FreeAMMediaType(mediaType); } } } } catch (Exception ex) { Console.WriteLine(ex.Message); } } } }