202508081811

包帳 State 没變 不重復做
VideoPlayForm 大調整
This commit is contained in:
jasonchenwork 2025-08-07 18:34:45 +08:00
parent 5ff2e096fc
commit f8a6e5c40d
5 changed files with 722 additions and 726 deletions

View File

@ -13,7 +13,7 @@ namespace DualScreenDemo
private static PrimaryForm primaryForm; // 儲存實例的參考 private static PrimaryForm primaryForm; // 儲存實例的參考
public static Room room = new Room(); public static Room room = new Room();
public static string verSion = "Server V2.7 202508041725"; public static string verSion = "Server V2.8 202508071811";
[STAThread] [STAThread]
static void Main() static void Main()
@ -82,9 +82,6 @@ namespace DualScreenDemo
// 显示 videoPlayerForm 在第二显示器 // 显示 videoPlayerForm 在第二显示器
primaryForm.videoPlayerForm.Show(); primaryForm.videoPlayerForm.Show();
// 初始化公共播放列表
primaryForm.videoPlayerForm.PlayNextSong();
} }
} }
primaryForm.Show(); primaryForm.Show();

12
Room.cs
View File

@ -65,23 +65,27 @@ namespace DualScreenDemo
} }
public void set(string value) public void set(string value)
{ {
State =getDB(); string StateDB=getDB();
Console.WriteLine($"hostname status: {hostName},{State},{startedAt},{endedAt}");
Console.WriteLine($"hostname status: {hostName},{StateDB},{startedAt},{endedAt} ,{State}");
string marqueeMessage = "歡迎使用超級巨星歡唱,與你共度美好時光。"; string marqueeMessage = "歡迎使用超級巨星歡唱,與你共度美好時光。";
Color c = Color.White; Color c = Color.White;
if (State.Equals("fire")) if (StateDB.Equals("fire"))
{ {
PrimaryForm.Instance.ShowSendOffScreen(); PrimaryForm.Instance.ShowSendOffScreen();
VideoPlayerForm.Instance.Pause(); VideoPlayerForm.Instance.Pause();
marqueeMessage = "發生火災,請跟隨引導至逃生出口!!!"; marqueeMessage = "發生火災,請跟隨引導至逃生出口!!!";
c = Color.Red; c = Color.Red;
} }
else if (State.Equals("active")) else if (StateDB.Equals("active"))
{
if (!State.Equals(StateDB))
{ {
DBObj.SongList.clearSong(); DBObj.SongList.clearSong();
PrimaryForm.Instance.HotPlayButton_Click(null, EventArgs.Empty); PrimaryForm.Instance.HotPlayButton_Click(null, EventArgs.Empty);
PrimaryForm.Instance.HideSendOffScreen(); PrimaryForm.Instance.HideSendOffScreen();
} }
}
else else
{ {
DBObj.SongList.roomClose(); DBObj.SongList.roomClose();

370
Services/MediaService.cs Normal file
View 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;
}
}
}
}

View 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;
}
}
}
}

View File

@ -1,8 +1,10 @@
using System.IO; // For StreamWriter using System.IO; // For StreamWriter
using System.Runtime.InteropServices; using System.Runtime.InteropServices;
using System.Windows.Media;
using DirectShowLib; using DirectShowLib;
using DBObj; using DBObj;
using OverlayFormObj; using OverlayFormObj;
using DualScreenDemo.Services;
namespace DualScreenDemo namespace DualScreenDemo
{ {
public class VideoPlayerForm : Form public class VideoPlayerForm : Form
@ -67,27 +69,8 @@ namespace DualScreenDemo
private const int GWL_EXSTYLE = -20; private const int GWL_EXSTYLE = -20;
private const int WS_EX_TOPMOST = 0x00000008; private const int WS_EX_TOPMOST = 0x00000008;
private const uint SWP_NOZORDER = 0x0004; private const uint SWP_NOZORDER = 0x0004;
private MediaServicePrimary primary = new MediaServicePrimary();
private IGraphBuilder graphBuilderPrimary; private MediaService secondary = new MediaService();
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 static OverlayForm overlayForm;
public bool isMuted = false; public bool isMuted = false;
@ -102,9 +85,12 @@ namespace DualScreenDemo
} }
private static Screen secondMonitor; private static Screen secondMonitor;
private int newWidth;
private int newHeight;
public VideoPlayerForm() public VideoPlayerForm()
{ {
Lold();
Instance = this; Instance = this;
// this.DoubleBuffered = true; // this.DoubleBuffered = true;
this.Load += VideoPlayerForm_Load; this.Load += VideoPlayerForm_Load;
@ -112,10 +98,35 @@ namespace DualScreenDemo
this.FormClosing += VideoPlayerForm_FormClosing; this.FormClosing += VideoPlayerForm_FormClosing;
InitializeOverlayForm(secondMonitor); InitializeOverlayForm(secondMonitor);
BringOverlayToFront(); BringOverlayToFront();
HttpServer.OnDisplayBarrage += DisplayBarrageOnOverlay; HttpServer.OnDisplayBarrage += DisplayBarrageOnOverlay;
MonitorMediaEvents(); 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) private void VideoPlayerForm_Load(object sender, EventArgs e)
{ {
@ -128,38 +139,27 @@ namespace DualScreenDemo
this.Size = secondMonitor.Bounds.Size; this.Size = secondMonitor.Bounds.Size;
// this.DoubleBuffered = true; // this.DoubleBuffered = true;
} }
CheckMonitor(); Screen screen = Screen.FromHandle(this.Handle);
} }
private void VideoPlayerForm_Shown(object sender, EventArgs e) private void VideoPlayerForm_Shown(object sender, EventArgs e)
{ {
int hr = CoInitializeEx(IntPtr.Zero, COINIT.APARTMENTTHREADED); int hr = CoInitializeEx(IntPtr.Zero, COINIT.APARTMENTTHREADED);
if (hr < 0) { if (hr < 0)
{
Console.WriteLine("Failed to initialize COM library."); Console.WriteLine("Failed to initialize COM library.");
return; return;
} }
else
InitializeGraphBuilderPrimary(); {
InitializeGraphBuilderSecondary(); PlayNextSong();
}
} }
private void VideoPlayerForm_FormClosing(object sender, FormClosingEventArgs e) private void VideoPlayerForm_FormClosing(object sender, FormClosingEventArgs e)
{ {
if (videoWindowPrimary != null) primary.VideoPlayerFormClosing();
{ secondary.VideoPlayerFormClosing();
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 // 清理COM
CoUninitialize(); CoUninitialize();
} }
@ -178,175 +178,6 @@ namespace DualScreenDemo
MULTITHREADED = 0x0 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() public void SyncToPrimaryMonitor()
{ {
@ -382,53 +213,7 @@ namespace DualScreenDemo
PrimaryForm.Instance.syncMicDownButton.BringToFront(); PrimaryForm.Instance.syncMicDownButton.BringToFront();
PrimaryForm.Instance.syncCloseButton.Visible = true; PrimaryForm.Instance.syncCloseButton.Visible = true;
PrimaryForm.Instance.syncCloseButton.BringToFront(); 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() public void ClosePrimaryScreenPanel()
{ {
@ -447,12 +232,6 @@ namespace DualScreenDemo
PrimaryForm.Instance.syncMicUpButton.Visible = false; PrimaryForm.Instance.syncMicUpButton.Visible = false;
PrimaryForm.Instance.syncMicDownButton.Visible = false; PrimaryForm.Instance.syncMicDownButton.Visible = false;
PrimaryForm.Instance.syncCloseButton.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; IsSyncToPrimaryMonitor = false;
} }
catch (Exception ex) catch (Exception ex)
@ -483,15 +262,10 @@ namespace DualScreenDemo
public async Task PlayNextSong() public async Task PlayNextSong()
{ {
// 等待初始化完成(例如播放器、串口等資源尚未就緒時) // 等待初始化完成(例如播放器、串口等資源尚未就緒時)
while (!isInitializationComplete)
{
await Task.Delay(100);
}
StopAndReleaseResources();
await Task.Delay(100);
Console.WriteLine("開始播放下一首歌曲..."); Console.WriteLine("開始播放下一首歌曲...");
var songToPlay = SongList.Next(); var songToPlay = SongList.Next();
if (!songToPlay.isPublicSong) { if (!songToPlay.isPublicSong)
{
// 若是使用者點播模式先送出升Key的串口指令 // 若是使用者點播模式先送出升Key的串口指令
if (SerialPortManager.mySerialPort != null && SerialPortManager.mySerialPort.IsOpen) if (SerialPortManager.mySerialPort != null && SerialPortManager.mySerialPort.IsOpen)
{ {
@ -512,32 +286,49 @@ namespace DualScreenDemo
// 隱藏「暫停中」標籤 // 隱藏「暫停中」標籤
overlayForm.HidePauseLabel(); 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 try
{ {
// 確保在UI線程上執行COM對象操作 if (InvokeRequired)
if (this.InvokeRequired) {
this.Invoke(new Action(async () => { await InitializeAndPlayMedia(pathToPlay); })); await InvokeAsync(() => InitializeAndPlayMedia(pathToPlay));
}
else else
{
await InitializeAndPlayMedia(pathToPlay); await InitializeAndPlayMedia(pathToPlay);
} }
}
catch (Exception ex) catch (Exception ex)
{ {
Console.WriteLine($"播放時發生錯誤: {ex.Message}"); Console.WriteLine($"播放時發生錯誤: {ex.Message}");
// 嘗試重新初始化並重播 await RetryInitializeAndPlayMedia(pathToPlay);
}
}
private async Task RetryInitializeAndPlayMedia(string path)
{
try try
{ {
await Task.Delay(100);
StopAndReleaseResources();
await Task.Delay(100);
// 重新初始化 COM
int hr = CoInitializeEx(IntPtr.Zero, COINIT.APARTMENTTHREADED); int hr = CoInitializeEx(IntPtr.Zero, COINIT.APARTMENTTHREADED);
if (hr >= 0) if (hr >= 0)
{ {
InitializeGraphBuilderPrimary(); await InitializeAndPlayMedia(path);
InitializeGraphBuilderSecondary(); }
await InitializeAndPlayMedia(pathToPlay); else
{
Console.WriteLine("CoInitializeEx 失敗,無法重新初始化 COM。");
} }
} }
catch (Exception retryEx) catch (Exception retryEx)
@ -545,248 +336,46 @@ namespace DualScreenDemo
Console.WriteLine($"重試播放時發生錯誤: {retryEx.Message}"); 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); primary.RenderMediaFile(pathToPlay,PrimaryForm.Instance.primaryScreenPanel.Handle,newWidth,newHeight);
RenderMediaFileSecondary(pathToPlay); secondary.RenderMediaFile(pathToPlay,this.Handle,secondMonitor.Bounds.Width,secondMonitor.Bounds.Height);
// 綁定視頻窗口到副屏幕
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) if (isMuted)
{ {
SetVolume(-10000); 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 else
{ {
Console.WriteLine("Failed to render secondary file."); SetVolume(previousVolume);
} }
} // 開始播放
primary.Run();
public static void SaveGraphFile(IGraphBuilder graph, string filename) secondary.Run();
{ if (isSyncToPrimaryMonitor) SyncToPrimaryMonitor();
var writer = new StreamWriter(filename); return Task.CompletedTask;
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) private void InitializeOverlayForm(Screen secondaryScreen)
@ -805,67 +394,31 @@ namespace DualScreenDemo
this.Focus(); this.Focus();
} }
private bool isPlayingNext = false;
public void MonitorMediaEvents() public void MonitorMediaEvents()
{ {
Console.WriteLine("開始監聽媒體事件...");
Task.Run(async () => Task.Run(async () =>
{ {
Console.WriteLine("開始監聽媒體事件...");
while (true) while (true)
{ {
try 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 () => BeginInvoke(new Action(async () =>
{ {
StopAndReleaseResources();
await Task.Delay(100); // 给予足够的时间释放资源
await PlayNextSong(); await PlayNextSong();
})); }));
}
}
} }
} }
catch (Exception ex) catch (Exception ex)
{ {
Console.WriteLine($"監控媒體事件時發生錯誤: {ex.Message}"); Console.WriteLine($"監控媒體事件時發生錯誤: {ex.Message}");
isPlayingNext = false;
await Task.Delay(1000); await Task.Delay(1000);
} }
await Task.Delay(500); await Task.Delay(1000);
} }
}); });
} }
@ -886,28 +439,22 @@ namespace DualScreenDemo
public void Play() public void Play()
{ {
if (mediaControlPrimary != null) primary.Run();
mediaControlPrimary.Run(); secondary.Run();
if (mediaControlSecondary != null)
mediaControlSecondary.Run();
isPaused = false; isPaused = false;
OverlayForm.MainForm.HidePauseLabel(); OverlayForm.MainForm.HidePauseLabel();
} }
public void Stop() public void Stop()
{ {
if (mediaControlPrimary != null) primary.Stop();
mediaControlPrimary.Stop(); secondary.Stop();
if (mediaControlSecondary != null)
mediaControlSecondary.Stop();
} }
public void Pause() public void Pause()
{ {
if (mediaControlPrimary != null) primary.Pause();
mediaControlPrimary.Pause(); secondary.Pause();
if (mediaControlSecondary != null)
mediaControlSecondary.Pause();
isPaused = true; isPaused = true;
OverlayForm.MainForm.ShowPauseLabel(); OverlayForm.MainForm.ShowPauseLabel();
} }
@ -946,149 +493,17 @@ namespace DualScreenDemo
} }
public void SetVolume(int volume) public void SetVolume(int volume)
{ {
if (audioRenderer != null) secondary.SetVolume(volume);
{
IBasicAudio basicAudio = audioRenderer as IBasicAudio;
if (basicAudio != null)
{
basicAudio.put_Volume(volume);
}
}
} }
public int GetVolume() public int GetVolume()
{ {
if (audioRenderer != null) return secondary.GetVolume();
{
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() public void ToggleVocalRemoval()
{ {
try OverlayForm.MainForm.ShowTopRightLabelTime(secondary.ToggleVocalRemoval() ? "無人聲" : "有人聲");
{
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);
}
}
} }
} }