2025-08-07 18:34:45 +08:00
|
|
|
using DirectShowLib;
|
|
|
|
using System;
|
|
|
|
using System.Runtime.InteropServices;
|
|
|
|
|
|
|
|
namespace DualScreenDemo.Services
|
|
|
|
{
|
2025-08-08 16:42:15 +08:00
|
|
|
public class MediaService : Video
|
2025-08-07 18:34:45 +08:00
|
|
|
{
|
|
|
|
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); // 显示窗口
|
|
|
|
}
|
|
|
|
}
|
|
|
|
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);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|