202508081811
包帳 State 没變 不重復做 VideoPlayForm 大調整
This commit is contained in:
parent
5ff2e096fc
commit
f8a6e5c40d
@ -13,7 +13,7 @@ namespace DualScreenDemo
|
||||
private static PrimaryForm primaryForm; // 儲存實例的參考
|
||||
public static Room room = new Room();
|
||||
|
||||
public static string verSion = "Server V2.7 202508041725";
|
||||
public static string verSion = "Server V2.8 202508071811";
|
||||
|
||||
[STAThread]
|
||||
static void Main()
|
||||
@ -82,9 +82,6 @@ namespace DualScreenDemo
|
||||
|
||||
// 显示 videoPlayerForm 在第二显示器
|
||||
primaryForm.videoPlayerForm.Show();
|
||||
|
||||
// 初始化公共播放列表
|
||||
primaryForm.videoPlayerForm.PlayNextSong();
|
||||
}
|
||||
}
|
||||
primaryForm.Show();
|
||||
|
12
Room.cs
12
Room.cs
@ -65,23 +65,27 @@ namespace DualScreenDemo
|
||||
}
|
||||
public void set(string value)
|
||||
{
|
||||
State =getDB();
|
||||
Console.WriteLine($"hostname status: {hostName},{State},{startedAt},{endedAt}");
|
||||
string StateDB=getDB();
|
||||
|
||||
Console.WriteLine($"hostname status: {hostName},{StateDB},{startedAt},{endedAt} ,{State}");
|
||||
string marqueeMessage = "歡迎使用超級巨星歡唱,與你共度美好時光。";
|
||||
Color c = Color.White;
|
||||
if (State.Equals("fire"))
|
||||
if (StateDB.Equals("fire"))
|
||||
{
|
||||
PrimaryForm.Instance.ShowSendOffScreen();
|
||||
VideoPlayerForm.Instance.Pause();
|
||||
marqueeMessage = "發生火災,請跟隨引導至逃生出口!!!";
|
||||
c = Color.Red;
|
||||
}
|
||||
else if (State.Equals("active"))
|
||||
else if (StateDB.Equals("active"))
|
||||
{
|
||||
if (!State.Equals(StateDB))
|
||||
{
|
||||
DBObj.SongList.clearSong();
|
||||
PrimaryForm.Instance.HotPlayButton_Click(null, EventArgs.Empty);
|
||||
PrimaryForm.Instance.HideSendOffScreen();
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
DBObj.SongList.roomClose();
|
||||
|
370
Services/MediaService.cs
Normal file
370
Services/MediaService.cs
Normal file
@ -0,0 +1,370 @@
|
||||
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<T>(ref T comObject) where T : class
|
||||
{
|
||||
if (comObject != null)
|
||||
{
|
||||
Marshal.ReleaseComObject(comObject);
|
||||
comObject = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
210
Services/MediaServicePrimary.cs
Normal file
210
Services/MediaServicePrimary.cs
Normal file
@ -0,0 +1,210 @@
|
||||
using DirectShowLib;
|
||||
using System;
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
namespace DualScreenDemo.Services
|
||||
{
|
||||
public class MediaServicePrimary
|
||||
{
|
||||
IGraphBuilder graphBuilder;
|
||||
IBaseFilter sourceFilter;
|
||||
IBaseFilter lavSplitter;
|
||||
IBaseFilter lavVideoDecoder;
|
||||
IBaseFilter videoRenderer;
|
||||
IVideoWindow videoWindow;
|
||||
IMediaControl mediaControl;
|
||||
|
||||
|
||||
public MediaServicePrimary(){}
|
||||
|
||||
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 sourceFilter);
|
||||
SafeRelease(ref lavVideoDecoder);
|
||||
SafeRelease(ref lavSplitter);
|
||||
SafeRelease(ref graphBuilder);
|
||||
}
|
||||
|
||||
public void RenderMediaFile(string filePath, nint Handle, int Width, int Height)
|
||||
{
|
||||
StopAndReleaseResources();
|
||||
if (videoWindow != null) videoWindow.put_Visible(OABool.False);
|
||||
|
||||
int hr;
|
||||
graphBuilder = (IGraphBuilder)new FilterGraph();
|
||||
if (graphBuilder == null)
|
||||
throw new Exception("Failed to create FilterGraph for primary monitor.");
|
||||
|
||||
graphBuilder.AddSourceFilter(filePath, "Source", out sourceFilter);
|
||||
try
|
||||
{
|
||||
lavSplitter = AddFilterByClsid(graphBuilder, "LAV Splitter", Clsid.LAVSplitter);
|
||||
lavVideoDecoder = AddFilterByClsid(graphBuilder, "LAV Video Decoder", Clsid.LAVVideoDecoder);
|
||||
videoRenderer = AddFilterByClsid(graphBuilder, "Primary Video Renderer", Clsid.VideoRenderer);
|
||||
hr = graphBuilder.AddFilter(videoRenderer, "Primary Video Renderer");
|
||||
DsError.ThrowExceptionForHR(hr);
|
||||
|
||||
mediaControl = (IMediaControl)graphBuilder;
|
||||
if (mediaControl == null)
|
||||
{
|
||||
Console.WriteLine("Failed to get Media Control for primary monitor.");
|
||||
return;
|
||||
}
|
||||
hr = ConnectFilters(graphBuilder, sourceFilter, "Output", lavSplitter, "Input");
|
||||
DsError.ThrowExceptionForHR(hr);
|
||||
hr = ConnectFilters(graphBuilder, lavSplitter, "Video", lavVideoDecoder, "Input");
|
||||
DsError.ThrowExceptionForHR(hr);
|
||||
hr = ConnectFilters(graphBuilder, lavVideoDecoder, "Output", videoRenderer, "VMR Input0");
|
||||
DsError.ThrowExceptionForHR(hr);
|
||||
|
||||
try
|
||||
{
|
||||
|
||||
IVideoWindow videoWindow = (IVideoWindow)videoRenderer;
|
||||
videoWindow.put_Owner(Handle);
|
||||
videoWindow.put_WindowStyle(WindowStyle.Child | WindowStyle.ClipChildren);
|
||||
|
||||
// ⬅ 左上角對齊(固定從 0,0 開始)
|
||||
videoWindow.SetWindowPosition(0, 0, Width, Height);
|
||||
|
||||
videoWindow.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));
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Console.WriteLine("Error initializing graph builder for primary monitor: " + ex.Message);
|
||||
}
|
||||
}
|
||||
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 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 SafeRelease<T>(ref T comObject) where T : class
|
||||
{
|
||||
if (comObject != null)
|
||||
{
|
||||
Marshal.ReleaseComObject(comObject);
|
||||
comObject = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -1,8 +1,10 @@
|
||||
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
|
||||
@ -67,27 +69,8 @@ namespace DualScreenDemo
|
||||
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;
|
||||
private MediaServicePrimary primary = new MediaServicePrimary();
|
||||
private MediaService secondary = new MediaService();
|
||||
|
||||
public static OverlayForm overlayForm;
|
||||
public bool isMuted = false;
|
||||
@ -102,9 +85,12 @@ namespace DualScreenDemo
|
||||
}
|
||||
|
||||
private static Screen secondMonitor;
|
||||
private int newWidth;
|
||||
private int newHeight;
|
||||
|
||||
public VideoPlayerForm()
|
||||
{
|
||||
Lold();
|
||||
Instance = this;
|
||||
// this.DoubleBuffered = true;
|
||||
this.Load += VideoPlayerForm_Load;
|
||||
@ -112,10 +98,35 @@ namespace DualScreenDemo
|
||||
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)
|
||||
{
|
||||
@ -128,38 +139,27 @@ namespace DualScreenDemo
|
||||
this.Size = secondMonitor.Bounds.Size;
|
||||
// this.DoubleBuffered = true;
|
||||
}
|
||||
CheckMonitor();
|
||||
Screen screen = Screen.FromHandle(this.Handle);
|
||||
}
|
||||
|
||||
private void VideoPlayerForm_Shown(object sender, EventArgs e)
|
||||
{
|
||||
int hr = CoInitializeEx(IntPtr.Zero, COINIT.APARTMENTTHREADED);
|
||||
if (hr < 0) {
|
||||
if (hr < 0)
|
||||
{
|
||||
Console.WriteLine("Failed to initialize COM library.");
|
||||
return;
|
||||
}
|
||||
|
||||
InitializeGraphBuilderPrimary();
|
||||
InitializeGraphBuilderSecondary();
|
||||
else
|
||||
{
|
||||
PlayNextSong();
|
||||
}
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
primary.VideoPlayerFormClosing();
|
||||
secondary.VideoPlayerFormClosing();
|
||||
// 清理COM
|
||||
CoUninitialize();
|
||||
}
|
||||
@ -178,175 +178,6 @@ namespace DualScreenDemo
|
||||
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()
|
||||
{
|
||||
@ -382,53 +213,7 @@ namespace DualScreenDemo
|
||||
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()
|
||||
{
|
||||
@ -447,12 +232,6 @@ namespace DualScreenDemo
|
||||
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)
|
||||
@ -483,15 +262,10 @@ namespace DualScreenDemo
|
||||
public async Task PlayNextSong()
|
||||
{
|
||||
// 等待初始化完成(例如播放器、串口等資源尚未就緒時)
|
||||
while (!isInitializationComplete)
|
||||
{
|
||||
await Task.Delay(100);
|
||||
}
|
||||
StopAndReleaseResources();
|
||||
await Task.Delay(100);
|
||||
Console.WriteLine("開始播放下一首歌曲...");
|
||||
var songToPlay = SongList.Next();
|
||||
if (!songToPlay.isPublicSong) {
|
||||
if (!songToPlay.isPublicSong)
|
||||
{
|
||||
// 若是使用者點播模式,先送出升Key的串口指令
|
||||
if (SerialPortManager.mySerialPort != null && SerialPortManager.mySerialPort.IsOpen)
|
||||
{
|
||||
@ -512,32 +286,49 @@ namespace DualScreenDemo
|
||||
// 隱藏「暫停中」標籤
|
||||
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
|
||||
{
|
||||
// 確保在UI線程上執行COM對象操作
|
||||
if (this.InvokeRequired)
|
||||
this.Invoke(new Action(async () => { await InitializeAndPlayMedia(pathToPlay); }));
|
||||
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
|
||||
{
|
||||
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);
|
||||
await InitializeAndPlayMedia(path);
|
||||
}
|
||||
else
|
||||
{
|
||||
Console.WriteLine("CoInitializeEx 失敗,無法重新初始化 COM。");
|
||||
}
|
||||
}
|
||||
catch (Exception retryEx)
|
||||
@ -545,248 +336,46 @@ namespace DualScreenDemo
|
||||
Console.WriteLine($"重試播放時發生錯誤: {retryEx.Message}");
|
||||
}
|
||||
}
|
||||
|
||||
// 通用的 async invoke 方法(避免重複寫)
|
||||
private Task InvokeAsync(Func<Task> func)
|
||||
{
|
||||
var tcs = new TaskCompletionSource<bool>();
|
||||
BeginInvoke(async () =>
|
||||
{
|
||||
try
|
||||
{
|
||||
await func();
|
||||
tcs.SetResult(true);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
tcs.SetException(ex);
|
||||
}
|
||||
});
|
||||
return tcs.Task;
|
||||
}
|
||||
|
||||
private async Task InitializeAndPlayMedia(string pathToPlay)
|
||||
private 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);
|
||||
}
|
||||
primary.RenderMediaFile(pathToPlay,PrimaryForm.Instance.primaryScreenPanel.Handle,newWidth,newHeight);
|
||||
secondary.RenderMediaFile(pathToPlay,this.Handle,secondMonitor.Bounds.Width,secondMonitor.Bounds.Height);
|
||||
|
||||
// 音量處理
|
||||
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.");
|
||||
SetVolume(previousVolume);
|
||||
}
|
||||
}
|
||||
|
||||
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);
|
||||
// 開始播放
|
||||
primary.Run();
|
||||
secondary.Run();
|
||||
if (isSyncToPrimaryMonitor) SyncToPrimaryMonitor();
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
private void InitializeOverlayForm(Screen secondaryScreen)
|
||||
@ -805,67 +394,31 @@ namespace DualScreenDemo
|
||||
this.Focus();
|
||||
}
|
||||
|
||||
private bool isPlayingNext = false;
|
||||
|
||||
public void MonitorMediaEvents()
|
||||
{
|
||||
Console.WriteLine("開始監聽媒體事件...");
|
||||
|
||||
Task.Run(async () =>
|
||||
{
|
||||
Console.WriteLine("開始監聽媒體事件...");
|
||||
while (true)
|
||||
{
|
||||
try
|
||||
{
|
||||
if (mediaControlSecondary == null)
|
||||
bool isAtEnd = secondary.isAtEnd() ;
|
||||
if (isAtEnd && !isPaused)
|
||||
{
|
||||
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);
|
||||
await Task.Delay(1000);
|
||||
}
|
||||
});
|
||||
}
|
||||
@ -886,28 +439,22 @@ namespace DualScreenDemo
|
||||
|
||||
public void Play()
|
||||
{
|
||||
if (mediaControlPrimary != null)
|
||||
mediaControlPrimary.Run();
|
||||
if (mediaControlSecondary != null)
|
||||
mediaControlSecondary.Run();
|
||||
primary.Run();
|
||||
secondary.Run();
|
||||
isPaused = false;
|
||||
OverlayForm.MainForm.HidePauseLabel();
|
||||
}
|
||||
|
||||
public void Stop()
|
||||
{
|
||||
if (mediaControlPrimary != null)
|
||||
mediaControlPrimary.Stop();
|
||||
if (mediaControlSecondary != null)
|
||||
mediaControlSecondary.Stop();
|
||||
primary.Stop();
|
||||
secondary.Stop();
|
||||
}
|
||||
|
||||
public void Pause()
|
||||
{
|
||||
if (mediaControlPrimary != null)
|
||||
mediaControlPrimary.Pause();
|
||||
if (mediaControlSecondary != null)
|
||||
mediaControlSecondary.Pause();
|
||||
primary.Pause();
|
||||
secondary.Pause();
|
||||
isPaused = true;
|
||||
OverlayForm.MainForm.ShowPauseLabel();
|
||||
}
|
||||
@ -946,149 +493,17 @@ namespace DualScreenDemo
|
||||
}
|
||||
public void SetVolume(int volume)
|
||||
{
|
||||
if (audioRenderer != null)
|
||||
{
|
||||
IBasicAudio basicAudio = audioRenderer as IBasicAudio;
|
||||
if (basicAudio != null)
|
||||
{
|
||||
basicAudio.put_Volume(volume);
|
||||
}
|
||||
}
|
||||
secondary.SetVolume(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 secondary.GetVolume();
|
||||
}
|
||||
}
|
||||
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;
|
||||
OverlayForm.MainForm.ShowTopRightLabelTime(secondary.ToggleVocalRemoval() ? "無人聲" : "有人聲");
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
|
Loading…
x
Reference in New Issue
Block a user