202508130945

更換播放器 使用VLC
加入dbChange,situation
調整全部靜音寫法
This commit is contained in:
jasonchenwork 2025-08-13 09:51:11 +08:00
parent b88b39c1c8
commit 732572bda2
14 changed files with 236 additions and 456 deletions

View File

@ -235,18 +235,13 @@ namespace DualScreenDemo
{ {
if (VideoPlayerForm.Instance.isMuted) if (VideoPlayerForm.Instance.isMuted)
{ {
VideoPlayerForm.Instance.Mute(false);
VideoPlayerForm.Instance.SetVolume(VideoPlayerForm.Instance.previousVolume);
VideoPlayerForm.Instance.isMuted = false; VideoPlayerForm.Instance.isMuted = false;
OverlayForm.MainForm.Invoke(new System.Action(() => OverlayForm.MainForm.HideMuteLabel())); OverlayForm.MainForm.Invoke(new System.Action(() => OverlayForm.MainForm.HideMuteLabel()));
} }
else else
{ {
VideoPlayerForm.Instance.Mute(true);
VideoPlayerForm.Instance.previousVolume = VideoPlayerForm.Instance.GetVolume();
VideoPlayerForm.Instance.SetVolume(-10000);
VideoPlayerForm.Instance.isMuted = true; VideoPlayerForm.Instance.isMuted = true;
OverlayForm.MainForm.Invoke(new System.Action(() => OverlayForm.MainForm.ShowMuteLabel())); OverlayForm.MainForm.Invoke(new System.Action(() => OverlayForm.MainForm.ShowMuteLabel()));
} }

View File

@ -120,7 +120,9 @@ namespace DBObj
db.Field<string>("artistA_simplified") ?? "", db.Field<string>("artistA_simplified") ?? "",
db.Field<string>("artistB_simplified") ?? "", db.Field<string>("artistB_simplified") ?? "",
db.Field<string>("song_simplified")?? "", db.Field<string>("song_simplified")?? "",
db.Field<int>("vocal") db.Field<int>("vocal"),
db.Field<int>("db_change"),
db.Field<string>("situation")
); );
} }

View File

@ -8,20 +8,27 @@ namespace DBObj
private string Name_Simplified; private string Name_Simplified;
private string FileName; private string FileName;
private int HumanVoice; private int HumanVoice;
private int DbChange;
private string Situation;
public Song(string num,string name,string name_s,string filename,int humanVoice) { public Song(string num, string name, string name_s, string filename, int humanVoice, int dbChange, string situation)
{
Number = num; Number = num;
Name = name; Name = name;
Name_Simplified = name_s; Name_Simplified = name_s;
FileName = filename; FileName = filename;
HumanVoice = humanVoice; HumanVoice = humanVoice;
DbChange = dbChange;
Situation = situation;
} }
public string getNumber() => Number; public string getNumber() => Number;
public string getName(bool IsSimplified) => IsSimplified ? Name_Simplified : Name; public string getName(bool IsSimplified) => IsSimplified ? Name_Simplified : Name;
public string getName() =>Name; public string getName() =>Name;
public string getFileName() => FileName; public string getFileName() => FileName;
public int getHumanVoice() => HumanVoice; public int getHumanVoice() => HumanVoice;
public int getDbChange() => DbChange;
public string getSituation() => Situation;
public string setNameTest(string s) => Name = Name + s; public string setNameTest(string s) => Name = Name + s;
} }
} }

View File

@ -14,12 +14,12 @@ namespace DBObj
public PlayState state; public PlayState state;
public SongData(string songNumber, string song, string filename, int humanVoice, bool isPublic) public SongData(string songNumber, string song, string filename, int humanVoice, bool isPublic)
{ {
basic=new(songNumber,song,"",filename,humanVoice); basic=new(songNumber,song,"",filename,humanVoice,0,"");
isPublicSong = isPublic; isPublicSong = isPublic;
} }
public SongData(string songNumber, string song, string artistA, string artistB, string filename, string artistASimplified, string artistBSimplified, string songSimplified, int humanVoice) public SongData(string songNumber, string song, string artistA, string artistB, string filename, string artistASimplified, string artistBSimplified, string songSimplified, int humanVoice,int dbChange,string situation)
{ {
basic=new(songNumber,song,songSimplified,filename,humanVoice); basic=new(songNumber,song,songSimplified,filename,humanVoice,dbChange,situation);
A = new Artist(artistA, artistASimplified); A = new Artist(artistA, artistASimplified);
B = !artistB.Equals("") ? new Artist(artistB, artistBSimplified) : null; B = !artistB.Equals("") ? new Artist(artistB, artistBSimplified) : null;
isPublicSong = false; isPublicSong = false;

View File

@ -482,7 +482,7 @@ namespace DualScreenDemo
if (PrimaryForm.Instance.videoPlayerForm.isMuted) if (PrimaryForm.Instance.videoPlayerForm.isMuted)
{ {
// 取消静音,恢复之前的音量 // 取消静音,恢复之前的音量
PrimaryForm.Instance.videoPlayerForm.SetVolume(PrimaryForm.Instance.videoPlayerForm.previousVolume); PrimaryForm.Instance.videoPlayerForm.Mute(false);
// muteButton.Text = "Mute"; // muteButton.Text = "Mute";
PrimaryForm.Instance.videoPlayerForm.isMuted = false; PrimaryForm.Instance.videoPlayerForm.isMuted = false;
OverlayForm.MainForm.HideMuteLabel(); OverlayForm.MainForm.HideMuteLabel();
@ -490,8 +490,7 @@ namespace DualScreenDemo
else else
{ {
// 静音,将音量设置为-10000 // 静音,将音量设置为-10000
PrimaryForm.Instance.videoPlayerForm.previousVolume = PrimaryForm.Instance.videoPlayerForm.GetVolume(); PrimaryForm.Instance.videoPlayerForm.Mute(true);
PrimaryForm.Instance.videoPlayerForm.SetVolume(-10000);
// muteButton.Text = "Unmute"; // muteButton.Text = "Unmute";
PrimaryForm.Instance.videoPlayerForm.isMuted = true; PrimaryForm.Instance.videoPlayerForm.isMuted = true;
OverlayForm.MainForm.ShowMuteLabel(); OverlayForm.MainForm.ShowMuteLabel();

View File

@ -46,7 +46,9 @@ namespace DualScreenDemo{
reader["artistA_simplified"].ToString(), reader["artistA_simplified"].ToString(),
reader["artistB_simplified"].ToString(), reader["artistB_simplified"].ToString(),
reader["song_simplified"].ToString(), reader["song_simplified"].ToString(),
Convert.ToInt32(reader["vocal"]) Convert.ToInt32(reader["vocal"]),
Convert.ToInt32(reader["db_change"]),
reader["situation"].ToString()
)); ));
} }
} }

View File

@ -5,6 +5,7 @@ namespace DualScreenDemo
public partial class PrimaryForm : Form public partial class PrimaryForm : Form
{ {
public Panel primaryScreenPanel; public Panel primaryScreenPanel;
public Panel videoPanel;
public Button syncServiceBellButton; public Button syncServiceBellButton;
public Button syncCutSongButton; public Button syncCutSongButton;
public Button syncReplayButton; public Button syncReplayButton;
@ -21,6 +22,7 @@ namespace DualScreenDemo
private void InitializeSyncScreen() private void InitializeSyncScreen()
{ {
this.primaryScreenPanel = new System.Windows.Forms.Panel(); this.primaryScreenPanel = new System.Windows.Forms.Panel();
this.videoPanel = new System.Windows.Forms.Panel();
this.syncServiceBellButton = new System.Windows.Forms.Button(); this.syncServiceBellButton = new System.Windows.Forms.Button();
this.syncCutSongButton = new System.Windows.Forms.Button(); this.syncCutSongButton = new System.Windows.Forms.Button();
this.syncReplayButton = new System.Windows.Forms.Button(); this.syncReplayButton = new System.Windows.Forms.Button();
@ -37,9 +39,13 @@ namespace DualScreenDemo
ResizeAndPositionControl(this.primaryScreenPanel, 0, 0, 1440, 900); ResizeAndPositionControl(this.primaryScreenPanel, 0, 0, 1440, 900);
this.primaryScreenPanel.TabIndex = 0; this.primaryScreenPanel.TabIndex = 0;
this.primaryScreenPanel.BorderStyle = BorderStyle.FixedSingle; this.primaryScreenPanel.BorderStyle = BorderStyle.None;
this.primaryScreenPanel.BackColor = System.Drawing.Color.Black; this.primaryScreenPanel.BackColor = System.Drawing.Color.Black;
ResizeAndPositionControl(this.videoPanel, 0, 0, 1200, 900);
this.videoPanel.TabIndex = 0;
this.videoPanel.BorderStyle = BorderStyle.None;
this.videoPanel.BackColor = System.Drawing.Color.Black;
var data=LoadConfigData(); var data=LoadConfigData();
// 同步畫面 服務鈴 // 同步畫面 服務鈴
@ -125,6 +131,7 @@ namespace DualScreenDemo
this.ClientSize = new System.Drawing.Size(1440, 900); this.ClientSize = new System.Drawing.Size(1440, 900);
this.Controls.Add(this.primaryScreenPanel); this.Controls.Add(this.primaryScreenPanel);
this.primaryScreenPanel.Controls.Add(this.videoPanel);
this.Controls.Add(this.syncCloseButton); this.Controls.Add(this.syncCloseButton);
this.Name = "PrimaryForm"; this.Name = "PrimaryForm";
this.ResumeLayout(false); this.ResumeLayout(false);

View File

@ -1894,21 +1894,15 @@ namespace DualScreenDemo
#region #region
private void MuteUnmuteButton_Click(object sender, EventArgs e) private void MuteUnmuteButton_Click(object sender, EventArgs e)
{ {
if (videoPlayerForm.isMuted) if (videoPlayerForm.isMuted)
{ {
videoPlayerForm.Mute(false);
videoPlayerForm.SetVolume(videoPlayerForm.previousVolume);
videoPlayerForm.isMuted = false; videoPlayerForm.isMuted = false;
OverlayForm.MainForm.HideMuteLabel(); OverlayForm.MainForm.HideMuteLabel();
} }
else else
{ {
videoPlayerForm.Mute(true);
videoPlayerForm.previousVolume = videoPlayerForm.GetVolume();
videoPlayerForm.SetVolume(-10000);
videoPlayerForm.isMuted = true; videoPlayerForm.isMuted = true;
OverlayForm.MainForm.ShowMuteLabel(); OverlayForm.MainForm.ShowMuteLabel();
} }

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.8 202508120940"; public static string verSion = "Server V2.8 202508130945";
[STAThread] [STAThread]
static void Main() static void Main()

View File

@ -1,234 +1,201 @@
using DirectShowLib; using LibVLCSharp.Shared;
using System;
using System.Runtime.InteropServices;
namespace DualScreenDemo.Services namespace DualScreenDemo.Services
{ {
public class MediaService : Video public class MediaService : IDisposable
{ {
public IGraphBuilder graphBuilder; private readonly LibVLC _libVLC;
private IMediaControl mediaControl; private readonly MediaPlayer _mediaPlayerPrimary;
public IBaseFilter renderer; private readonly MediaPlayer _mediaPlayerSecondary;
//private IBaseFilter audioRenderer; private Media? _media;
private IBaseFilter lavSplitter; private Media? _mediaNoAudio;
private IVideoWindow videoWindow; private bool _disposed;
public int Run() => (mediaControl != null) ? mediaControl.Run() : 0;
public int Stop() => (mediaControl != null) ? mediaControl.Stop() : 0;
public int Pause() => (mediaControl != null) ? mediaControl.Pause() : 0;
public void VideoPlayerForm_FormClosing() public MediaService()
{ {
if (videoWindow != null) Core.Initialize();
_libVLC = new LibVLC(
"--aout=directsound",
//"--avcodec-hw=dxva2",
"--network-caching=300",
"--file-caching=300",
"--audio-time-stretch"
);
_mediaPlayerPrimary = new MediaPlayer(_libVLC);
_mediaPlayerSecondary = new MediaPlayer(_libVLC);
}
#region Player Setup
public void SetVideoFormPrimary(nint handle, int width, int height)
{ {
videoWindow.put_Visible(OABool.False); _mediaPlayerPrimary.Hwnd = handle;
videoWindow.put_Owner(IntPtr.Zero); _mediaPlayerPrimary.AspectRatio = $"{width}:{height}";
Marshal.ReleaseComObject(videoWindow); _mediaPlayerPrimary.Scale = 0; // 保持原比例
videoWindow = null;
} }
public void SetVideoFormSecondary(nint handle, int width, int height)
{
_mediaPlayerSecondary.Hwnd = handle;
_mediaPlayerSecondary.AspectRatio = $"{width}:{height}";
_mediaPlayerSecondary.Scale = 0;
} }
#endregion
#region Playback
public MediaPlayer PrimaryPlayer => _mediaPlayerPrimary;
public MediaPlayer SecondaryPlayer => _mediaPlayerSecondary;
public bool IsPlaying => _mediaPlayerSecondary.IsPlaying;
public bool IsAtEnd() public bool IsAtEnd()
{ {
bool isAtEnd = false; var duration = _mediaPlayerSecondary.Media?.Duration ?? 0;
if (mediaControl == null) { return false; } var time = _mediaPlayerSecondary.Time;
IMediaSeeking mediaSeeking = graphBuilder as IMediaSeeking; return duration > 0 && Math.Abs(duration - time) < 1000;
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;
// 添加更严格的结束条件判断 public void LoadMedia(string filePath, int audioTrackIndex = 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}秒"); _media?.Dispose();
}
}
}
return isAtEnd;
}
public void RenderMediaFile(string filePath, nint Handle, int Width, int Height)
{
if (videoWindow != null) videoWindow.put_Visible(OABool.False);
Stop();
SafeRelease(ref renderer);
SafeRelease(ref lavSplitter);
SafeRelease(ref graphBuilder);
// 建立一個完整有聲音的 Media 給 secondary 播放器
_media = new Media(_libVLC, filePath, FromType.FromPath);
_media.AddOption(":audio-output=directsound");
_media.AddOption($":audio-track={audioTrackIndex}");
int hr = 0; // 同時準備給 primary 播放器用的無聲音版本 Media
graphBuilder = (IGraphBuilder)new FilterGraph(); _mediaNoAudio?.Dispose();
try _mediaNoAudio = new Media(_libVLC, filePath, FromType.FromPath);
_mediaNoAudio.AddOption(":no-audio"); // 關閉聲音輸出
}
public async Task PlayAsync()
{ {
lavSplitter = AddFilterByClsid(graphBuilder, "LAV Splitter", Clsid.LAVSplitter); if (_media == null || _mediaNoAudio == null) return;
AddFilterByClsid(graphBuilder, "LAV Video Decoder", Clsid.LAVVideoDecoder);
renderer = AddFilterByClsid(graphBuilder, "Secondary Video Renderer", Clsid.VideoRenderer); _mediaPlayerPrimary.Play(_mediaNoAudio); // 播放無聲音版本
//audioRenderer = AddFilterByClsid(graphBuilder, "Default DirectSound Device", Clsid.AudioRenderer); await Task.Delay(100);
mediaControl = (IMediaControl)graphBuilder; _mediaPlayerSecondary.Play(_media); // 播放有聲音版本
await SyncPlayersAsync();
} }
catch (Exception ex) public async Task Play()
{ {
Console.WriteLine("Error initializing graph builder with second monitor: " + ex.Message); _mediaPlayerPrimary.Play(); // 播放無聲音版本
await Task.Delay(100);
_mediaPlayerSecondary.Play(); // 播放有聲音版本
await SyncPlayersAsync();
} }
hr = graphBuilder.RenderFile(filePath, null);
if (hr == 0) public void Pause()
SetAudioTrackTo(1);
else
Console.WriteLine("Failed to render secondary file.");
// 绑定视频窗口到副屏幕
videoWindow = renderer as IVideoWindow;
if (videoWindow != null)
{ {
videoWindow.put_Owner(Handle); _mediaPlayerPrimary.Pause();
videoWindow.put_WindowStyle(WindowStyle.Child | WindowStyle.ClipChildren | WindowStyle.ClipSiblings); _mediaPlayerSecondary.Pause();
videoWindow.SetWindowPosition(0, 0, Width, Height);
videoWindow.put_Visible(OABool.True);
} }
mediaControl.Run();
public void Stop()
{
_mediaPlayerPrimary.Stop();
_mediaPlayerSecondary.Stop();
} }
#endregion
#region Sync
private async Task SyncPlayersAsync()
{
while (_mediaPlayerPrimary.IsPlaying && _mediaPlayerSecondary.IsPlaying)
{
var t1 = _mediaPlayerPrimary.Time;
var t2 = _mediaPlayerSecondary.Time;
if (Math.Abs(t1 - t2) > 100)
_mediaPlayerSecondary.Time = t1;
await Task.Delay(200);
}
}
#endregion
#region Mute
public bool Mute(bool isMuted)
{
_mediaPlayerSecondary.Mute = isMuted;
return _mediaPlayerSecondary.Mute;
}
#endregion
#region Volume
public void SetVolume(int volume) public void SetVolume(int volume)
{ {
if (graphBuilder != null) _mediaPlayerPrimary.Volume = volume;
{ _mediaPlayerSecondary.Volume = volume;
IBasicAudio basicAudio = graphBuilder as IBasicAudio;
if (basicAudio != null)
basicAudio.put_Volume( Math.Max(-10000, Math.Min(0, volume)));
} }
} public int GetVolume() => _mediaPlayerSecondary?.Volume ?? 0;
public int GetVolume() #endregion
{
if (graphBuilder != null)
{
IBasicAudio basicAudio = graphBuilder 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) #region Audio Tracks
public List<TrackDescription> GetAudioTracks()
{ {
int trackCount; var result = new List<TrackDescription>();
if (streamSelect.Count(out trackCount) == 0 && trackCount > 0) var media = _mediaPlayerSecondary.Media;
{
int currentTrackIndex = -1;
int audioTrack1 = -1;
int audioTrack2 = -1;
for (int i = 0; i < trackCount; i++) if (media == null) return result;
{
// 獲取音軌信息
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 (!media.IsParsed)
media.Parse(MediaParseOptions.ParseLocal);
if (mediaType.majorType == MediaType.Audio) var tracks = media.Tracks;
if (tracks == null) return result;
foreach (var track in tracks.Where(t => t.TrackType == TrackType.Audio))
{ {
if (audioTrack1 == -1) result.Add(new TrackDescription
{ {
audioTrack1 = i; Id = track.Id,
Name = !string.IsNullOrEmpty(track.Language) ? track.Language : $"Audio Track {track.Id}"
});
} }
else if (audioTrack2 == -1) return result;
{
audioTrack2 = i;
} }
if ((flags & AMStreamSelectInfoFlags.Enabled) != 0) public async Task SetAudioTrackToAsync(int trackIndex)
{ {
currentTrackIndex = i; var audioTracks = GetAudioTracks();
} if (trackIndex < 0 || trackIndex >= audioTracks.Count) return;
if (!_mediaPlayerSecondary.IsPlaying)
{
_mediaPlayerSecondary.Play();
await Task.Delay(500);
} }
DsUtils.FreeAMMediaType(mediaType); _mediaPlayerSecondary.SetAudioTrack(audioTracks[trackIndex].Id);
await Task.Delay(300);
}
#endregion
#region Dispose
public void Dispose()
{
if (_disposed) return;
_mediaPlayerPrimary?.Dispose();
_mediaPlayerSecondary?.Dispose();
_media?.Dispose();
_mediaNoAudio?.Dispose();
_libVLC?.Dispose();
_mediaNoAudio = null;
_media = null;
_disposed = true;
}
#endregion
} }
// 切換音軌 public class TrackDescription
if (currentTrackIndex == audioTrack1 && audioTrack2 != -1)
{ {
streamSelect.Enable(audioTrack2, AMStreamSelectEnableFlags.Enable); public int Id { get; set; }
isVocalRemoved = true; public string Name { get; set; } = string.Empty;
}
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);
}
}
} }
} }

View File

@ -1,92 +0,0 @@
using DirectShowLib;
using System;
using System.Runtime.InteropServices;
namespace DualScreenDemo.Services
{
public class MediaServicePrimary : Video
{
IGraphBuilder graphBuilder;
IBaseFilter lavSplitter;
IBaseFilter lavVideoDecoder;
public IBaseFilter videoRenderer;
IMediaControl mediaControl;
IBaseFilter sourceFilter;
private IVideoWindow videoWindow;
public MediaServicePrimary() { }
public int Run() => (mediaControl != null) ? mediaControl.Run() : 0;
public int Stop() => (mediaControl != null)? mediaControl.Stop() : 0;
public int Pause() => (mediaControl != null) ? mediaControl.Pause() : 0;
public void VideoPlayerForm_FormClosing() {
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 videoRenderer);
SafeRelease(ref sourceFilter);
SafeRelease(ref lavVideoDecoder);
SafeRelease(ref lavSplitter);
SafeRelease(ref graphBuilder);
}
public void RenderMediaFile(string filePath, nint Handle, int Width, int Height)
{
if (videoWindow != null) videoWindow.put_Visible(OABool.False);
StopAndReleaseResources();
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;
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);
videoWindow = videoRenderer as IVideoWindow;
if (videoWindow != null)
{
videoWindow.put_Owner(Handle);
videoWindow.put_WindowStyle(WindowStyle.Child | WindowStyle.ClipChildren);
videoWindow.SetWindowPosition(0, 0, Width, Height);
videoWindow.put_Visible(OABool.True);
}
mediaControl.Run();
}
catch (Exception ex)
{
Console.WriteLine("Error initializing graph builder for primary monitor: " + ex.Message);
}
}
}
}

View File

@ -1,75 +0,0 @@
using DirectShowLib;
using System.Runtime.InteropServices;
namespace DualScreenDemo.Services
{
public class Video
{
protected IBaseFilter AddFilterByClsid(IGraphBuilder graphBuilder, string name, Guid clsid)
{
try
{
Type filterType = Type.GetTypeFromCLSID(clsid);
object filterObject = Activator.CreateInstance(filterType);
IBaseFilter filter = filterObject as IBaseFilter;
if (filter == null)
{
IntPtr comObjectPointer = Marshal.GetIUnknownForObject(filterObject);
filter = (IBaseFilter)Marshal.GetObjectForIUnknown(comObjectPointer);
}
// 添加过滤器到图形构建器
int hr = graphBuilder.AddFilter(filter, name);
DsError.ThrowExceptionForHR(hr);
return filter;
}
catch (Exception ex)
{
Console.WriteLine($"Exception in AddFilterByClsid: {ex.Message}");
throw; // Rethrow the exception to handle it further up the call stack
}
}
protected 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;
}
protected 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+":"+pinInfo.name);
if (pinInfo.name == pinName)
{
return pins[0];
}
}
return null;
}
protected void SafeRelease<T>(ref T comObject) where T : class
{
if (comObject != null)
{
Marshal.ReleaseComObject(comObject);
comObject = null;
}
}
}
}

View File

@ -1,7 +1,4 @@
using System.IO; // For StreamWriter
using System.Runtime.InteropServices; using System.Runtime.InteropServices;
using System.Windows.Media;
using DirectShowLib;
using DBObj; using DBObj;
using OverlayFormObj; using OverlayFormObj;
using DualScreenDemo.Services; using DualScreenDemo.Services;
@ -69,14 +66,11 @@ 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 MediaServicePrimary primary = new MediaServicePrimary();
private MediaService secondary= new MediaService(); private MediaService secondary= new MediaService();
public static OverlayForm overlayForm; public static OverlayForm overlayForm;
public bool isMuted = false; public bool isMuted = false;
public int previousVolume = 0; public int previousVolume = 100;
public bool isPaused = false; public bool isPaused = false;
private bool isSyncToPrimaryMonitor = false; private bool isSyncToPrimaryMonitor = false;
@ -87,12 +81,9 @@ 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;
@ -103,32 +94,7 @@ namespace DualScreenDemo
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)
{ {
@ -154,14 +120,15 @@ namespace DualScreenDemo
} }
else else
{ {
secondary.SetVideoFormPrimary(PrimaryForm.Instance.videoPanel.Handle,PrimaryForm.Instance.videoPanel.Width,PrimaryForm.Instance.videoPanel.Height);
secondary.SetVideoFormSecondary(this.Handle,secondMonitor.Bounds.Width,secondMonitor.Bounds.Height);
PlayNextSong(); PlayNextSong();
} }
} }
private void VideoPlayerForm_FormClosing(object sender, FormClosingEventArgs e) private void VideoPlayerForm_FormClosing(object sender, FormClosingEventArgs e)
{ {
primary.VideoPlayerForm_FormClosing(); secondary.Dispose();
secondary.VideoPlayerForm_FormClosing();
// 清理COM // 清理COM
CoUninitialize(); CoUninitialize();
} }
@ -353,11 +320,11 @@ namespace DualScreenDemo
{ {
string pathToPlay = song.getFile(); string pathToPlay = song.getFile();
// 渲染媒體文件 // 渲染媒體文件
primary.RenderMediaFile(pathToPlay,PrimaryForm.Instance.primaryScreenPanel.Handle,newWidth,newHeight); secondary.LoadMedia(pathToPlay,song.isPublicSong ? 0 : 1);
secondary.RenderMediaFile(pathToPlay,this.Handle,secondMonitor.Bounds.Width,secondMonitor.Bounds.Height); secondary.PlayAsync();
// 音量處理 // 音量處理
SetVolume(isMuted ? -10000 : previousVolume); SetVolume(isMuted ? 0 : previousVolume);
if (isSyncToPrimaryMonitor) SyncToPrimaryMonitor(); if (isSyncToPrimaryMonitor) SyncToPrimaryMonitor();
return Task.CompletedTask; return Task.CompletedTask;
@ -389,8 +356,7 @@ namespace DualScreenDemo
{ {
try try
{ {
bool isAtEnd = secondary.IsAtEnd() ; if (secondary.IsAtEnd())
if (isAtEnd && !isPaused)
{ {
BeginInvoke(new Action(async () => BeginInvoke(new Action(async () =>
{ {
@ -425,21 +391,18 @@ namespace DualScreenDemo
public void Play() public void Play()
{ {
primary.Run(); secondary.Play();
secondary.Run();
isPaused = false; isPaused = false;
OverlayForm.MainForm.HidePauseLabel(); OverlayForm.MainForm.HidePauseLabel();
} }
public void Stop() public void Stop()
{ {
primary.Stop();
secondary.Stop(); secondary.Stop();
} }
public void Pause() public void Pause()
{ {
primary.Pause();
secondary.Pause(); secondary.Pause();
isPaused = true; isPaused = true;
OverlayForm.MainForm.ShowPauseLabel(); OverlayForm.MainForm.ShowPauseLabel();
@ -477,18 +440,26 @@ namespace DualScreenDemo
PrimaryForm.Instance.syncPauseButton.Visible = true; PrimaryForm.Instance.syncPauseButton.Visible = true;
} }
} }
public bool Mute(bool isMuted)
{
return secondary.Mute(isMuted);
}
public void SetVolume(int volume) public void SetVolume(int volume)
{ {
Console.WriteLine($"SetVolume: {volume}");
secondary.SetVolume(volume); secondary.SetVolume(volume);
} }
public int GetVolume() public int GetVolume()
{ {
return secondary.GetVolume(); return secondary.GetVolume();
} }
private bool isVocalRemoved = false;
public void ToggleVocalRemoval() public void ToggleVocalRemoval()
{ {
OverlayForm.MainForm.ShowTopRightLabelTime(secondary.ToggleVocalRemoval() ? "無人聲" : "有人聲"); isVocalRemoved=!isVocalRemoved;
int trackIndex = isVocalRemoved ? 1:0;
secondary.SetAudioTrackToAsync(trackIndex);
OverlayForm.MainForm.ShowTopRightLabelTime(isVocalRemoved ? "無人聲" : "有人聲");
} }
} }
} }

View File

@ -15,9 +15,12 @@
</PropertyGroup> </PropertyGroup>
<ItemGroup> <ItemGroup>
<PackageReference Include="LibVLCSharp" Version="3.9.4" />
<PackageReference Include="LibVLCSharp.WinForms" Version="3.9.4" />
<PackageReference Include="MySqlConnector" Version="2.4.0" /> <PackageReference Include="MySqlConnector" Version="2.4.0" />
<PackageReference Include="System.Data.SQLite.Core" Version="1.0.117" /> <PackageReference Include="System.Data.SQLite.Core" Version="1.0.117" />
<PackageReference Include="System.IO.Ports" Version="9.0.3" /> <PackageReference Include="System.IO.Ports" Version="9.0.3" />
<PackageReference Include="VideoLAN.LibVLC.Windows" Version="3.0.21" />
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<Reference Include="AxInterop.WMPLib"> <Reference Include="AxInterop.WMPLib">