using DirectShowLib; using System; using System.Runtime.InteropServices; namespace DualScreenDemo.Services { public class MediaService { public IGraphBuilder graphBuilder; private IMediaControl mediaControl; private IBaseFilter videoRenderer; private IBaseFilter lavSplitter; private IBaseFilter lavVideoDecoder; private IBaseFilter lavAudioDecoder; private IPin outputPin; private IBaseFilter audioRenderer; private IVideoWindow videoWindow; public int Run() { if (mediaControl != null) return mediaControl.Run(); else return 0; } public int Stop() { if (mediaControl != null) return mediaControl.Stop(); else return 0; } public int Pause() { if (mediaControl != null) return mediaControl.Pause(); else return 0; } public void VideoPlayerFormClosing() { if (videoWindow != null) { videoWindow.put_Visible(OABool.False); videoWindow.put_Owner(IntPtr.Zero); Marshal.ReleaseComObject(videoWindow); videoWindow = null; } } public void StopAndReleaseResources() { if (mediaControl != null) { mediaControl.Stop(); SafeRelease(ref mediaControl); } SafeRelease(ref videoWindow); SafeRelease(ref videoRenderer); SafeRelease(ref lavAudioDecoder); SafeRelease(ref outputPin); SafeRelease(ref lavVideoDecoder); SafeRelease(ref lavSplitter); SafeRelease(ref graphBuilder); } public bool isAtEnd() { bool isAtEnd = false; if (mediaControl == null) { return false; } IMediaSeeking mediaSeeking = graphBuilder as IMediaSeeking; if (mediaSeeking != null) { long currentPosition; long duration; if (mediaSeeking.GetCurrentPosition(out currentPosition) >= 0 && mediaSeeking.GetDuration(out duration) >= 0) { double currentSeconds = currentPosition / 10000000.0; double durationSeconds = duration / 10000000.0; // 添加更严格的结束条件判断 isAtEnd = durationSeconds > 0 && currentSeconds > 0 && Math.Abs(currentSeconds - durationSeconds) < 0.1; // 确保真的到了结尾 //Console.WriteLine($"檢測到歌曲 - 當前位置: {currentSeconds:F2}秒, 總時長: {durationSeconds:F2}秒"); if (isAtEnd) { Console.WriteLine($"檢測到歌曲 -結束: {currentSeconds:F2}秒, 總時長: {durationSeconds:F2}秒"); } } } return isAtEnd; } public void RenderMediaFile(string filePath, nint Handle, int Width, int Height) { StopAndReleaseResources(); if (videoWindow != null) videoWindow.put_Visible(OABool.False); int hr = 0; graphBuilder = (IGraphBuilder)new FilterGraph(); if (graphBuilder == null) { Console.WriteLine("Failed to create FilterGraph"); throw new Exception("Failed to create FilterGraph"); } try { lavSplitter = AddFilterByClsid(graphBuilder, "LAV Splitter", Clsid.LAVSplitter); lavVideoDecoder = AddFilterByClsid(graphBuilder, "LAV Video Decoder", Clsid.LAVVideoDecoder); lavAudioDecoder = AddFilterByClsid(graphBuilder, "LAV Audio Decoder", Clsid.LAVAudioDecoder); outputPin = FindPin(lavAudioDecoder, "Output"); videoRenderer = AddFilterByClsid(graphBuilder, "Secondary Video Renderer", Clsid.VideoRenderer); if (videoRenderer == null) { Console.WriteLine("Failed to initialize Secondary Video Renderer."); return; } hr = graphBuilder.AddFilter(videoRenderer, "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 = graphBuilder.AddFilter(audioRenderer, "Default DirectSound Device"); DsError.ThrowExceptionForHR(hr); mediaControl = (IMediaControl)graphBuilder; if (mediaControl == null) { Console.WriteLine("Failed to get Media Control"); return; } } catch (Exception ex) { Console.WriteLine("Error initializing graph builder with second monitor: " + ex.Message); } hr = graphBuilder.RenderFile(filePath, null); if (hr == 0) { Console.WriteLine("Secondary File rendered successfully."); SetAudioTrackTo(1); } else { Console.WriteLine("Failed to render secondary file."); } // 绑定视频窗口到副屏幕 videoWindow = videoRenderer as IVideoWindow; if (videoWindow != null) { videoWindow.put_Owner(Handle); // 副屏幕窗口句柄 videoWindow.put_WindowStyle(WindowStyle.Child | WindowStyle.ClipChildren | WindowStyle.ClipSiblings); videoWindow.SetWindowPosition(0, 0, Width, Height); videoWindow.put_Visible(OABool.True); // 显示窗口 } } 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 } } 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; } 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 bool ToggleVocalRemoval() { try { IAMStreamSelect streamSelect = lavSplitter 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; } } } } catch (Exception ex) { Console.WriteLine(ex.Message); } return isVocalRemoved; } public void SetAudioTrackTo(int trackIndex) { try { IAMStreamSelect streamSelect = lavSplitter 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); } } private void SafeRelease(ref T comObject) where T : class { if (comObject != null) { Marshal.ReleaseComObject(comObject); comObject = null; } } } }