From 732572bda23a36d81e13e2b56955afd385dc622b Mon Sep 17 00:00:00 2001 From: jasonchenwork Date: Wed, 13 Aug 2025 09:51:11 +0800 Subject: [PATCH] =?UTF-8?q?202508130945=20=E6=9B=B4=E6=8F=9B=E6=92=AD?= =?UTF-8?q?=E6=94=BE=E5=99=A8=20=E4=BD=BF=E7=94=A8VLC=20=E5=8A=A0=E5=85=A5?= =?UTF-8?q?dbChange,situation=20=E8=AA=BF=E6=95=B4=E5=85=A8=E9=83=A8?= =?UTF-8?q?=E9=9D=9C=E9=9F=B3=E5=AF=AB=E6=B3=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- CommandHandler.cs | 9 +- DBObj/SQLManager.cs | 4 +- DBObj/Song.cs | 25 +- DBObj/SongData.cs | 6 +- HttpServer.cs | 5 +- PrimaryFormParts/PrimaryForm.SQLSearch.cs | 4 +- PrimaryFormParts/PrimaryForm.SyncScreen.cs | 11 +- PrimaryFormParts/PrimaryForm.cs | 10 +- Program.cs | 2 +- Services/MediaService.cs | 373 ++++++++++----------- Services/MediaServicePrimary.cs | 92 ----- Services/Video.cs | 75 ----- VideoPlayerForm.cs | 73 ++-- superstar.csproj | 3 + 14 files changed, 236 insertions(+), 456 deletions(-) delete mode 100644 Services/MediaServicePrimary.cs delete mode 100644 Services/Video.cs diff --git a/CommandHandler.cs b/CommandHandler.cs index 1f8a7ce..6cc8997 100644 --- a/CommandHandler.cs +++ b/CommandHandler.cs @@ -235,18 +235,13 @@ namespace DualScreenDemo { if (VideoPlayerForm.Instance.isMuted) { - - VideoPlayerForm.Instance.SetVolume(VideoPlayerForm.Instance.previousVolume); - + VideoPlayerForm.Instance.Mute(false); VideoPlayerForm.Instance.isMuted = false; OverlayForm.MainForm.Invoke(new System.Action(() => OverlayForm.MainForm.HideMuteLabel())); } else { - - VideoPlayerForm.Instance.previousVolume = VideoPlayerForm.Instance.GetVolume(); - VideoPlayerForm.Instance.SetVolume(-10000); - + VideoPlayerForm.Instance.Mute(true); VideoPlayerForm.Instance.isMuted = true; OverlayForm.MainForm.Invoke(new System.Action(() => OverlayForm.MainForm.ShowMuteLabel())); } diff --git a/DBObj/SQLManager.cs b/DBObj/SQLManager.cs index 54baa7b..38fd881 100644 --- a/DBObj/SQLManager.cs +++ b/DBObj/SQLManager.cs @@ -120,7 +120,9 @@ namespace DBObj db.Field("artistA_simplified") ?? "", db.Field("artistB_simplified") ?? "", db.Field("song_simplified")?? "", - db.Field("vocal") + db.Field("vocal"), + db.Field("db_change"), + db.Field("situation") ); } diff --git a/DBObj/Song.cs b/DBObj/Song.cs index 2913e68..465f0aa 100644 --- a/DBObj/Song.cs +++ b/DBObj/Song.cs @@ -8,20 +8,27 @@ namespace DBObj private string Name_Simplified; private string FileName; private int HumanVoice; - - - public Song(string num,string name,string name_s,string filename,int humanVoice) { - Number=num; - Name=name; - Name_Simplified=name_s; - FileName=filename; - HumanVoice =humanVoice; + private int DbChange; + private string Situation; + + + public Song(string num, string name, string name_s, string filename, int humanVoice, int dbChange, string situation) + { + Number = num; + Name = name; + Name_Simplified = name_s; + FileName = filename; + HumanVoice = humanVoice; + DbChange = dbChange; + Situation = situation; } public string getNumber() => Number; public string getName(bool IsSimplified) => IsSimplified ? Name_Simplified : Name; public string getName() =>Name; public string getFileName() => FileName; public int getHumanVoice() => HumanVoice; - public string setNameTest(string s) => Name = Name+s; + public int getDbChange() => DbChange; + public string getSituation() => Situation; + public string setNameTest(string s) => Name = Name + s; } } \ No newline at end of file diff --git a/DBObj/SongData.cs b/DBObj/SongData.cs index 8f5112e..1b5dfe9 100644 --- a/DBObj/SongData.cs +++ b/DBObj/SongData.cs @@ -14,12 +14,12 @@ namespace DBObj public PlayState state; 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; } - 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); B = !artistB.Equals("") ? new Artist(artistB, artistBSimplified) : null; isPublicSong = false; diff --git a/HttpServer.cs b/HttpServer.cs index f14cd78..7382c4f 100644 --- a/HttpServer.cs +++ b/HttpServer.cs @@ -482,7 +482,7 @@ namespace DualScreenDemo if (PrimaryForm.Instance.videoPlayerForm.isMuted) { // 取消静音,恢复之前的音量 - PrimaryForm.Instance.videoPlayerForm.SetVolume(PrimaryForm.Instance.videoPlayerForm.previousVolume); + PrimaryForm.Instance.videoPlayerForm.Mute(false); // muteButton.Text = "Mute"; PrimaryForm.Instance.videoPlayerForm.isMuted = false; OverlayForm.MainForm.HideMuteLabel(); @@ -490,8 +490,7 @@ namespace DualScreenDemo else { // 静音,将音量设置为-10000 - PrimaryForm.Instance.videoPlayerForm.previousVolume = PrimaryForm.Instance.videoPlayerForm.GetVolume(); - PrimaryForm.Instance.videoPlayerForm.SetVolume(-10000); + PrimaryForm.Instance.videoPlayerForm.Mute(true); // muteButton.Text = "Unmute"; PrimaryForm.Instance.videoPlayerForm.isMuted = true; OverlayForm.MainForm.ShowMuteLabel(); diff --git a/PrimaryFormParts/PrimaryForm.SQLSearch.cs b/PrimaryFormParts/PrimaryForm.SQLSearch.cs index 85c5750..4b0cdb3 100644 --- a/PrimaryFormParts/PrimaryForm.SQLSearch.cs +++ b/PrimaryFormParts/PrimaryForm.SQLSearch.cs @@ -46,7 +46,9 @@ namespace DualScreenDemo{ reader["artistA_simplified"].ToString(), reader["artistB_simplified"].ToString(), reader["song_simplified"].ToString(), - Convert.ToInt32(reader["vocal"]) + Convert.ToInt32(reader["vocal"]), + Convert.ToInt32(reader["db_change"]), + reader["situation"].ToString() )); } } diff --git a/PrimaryFormParts/PrimaryForm.SyncScreen.cs b/PrimaryFormParts/PrimaryForm.SyncScreen.cs index 17cba01..f6de636 100644 --- a/PrimaryFormParts/PrimaryForm.SyncScreen.cs +++ b/PrimaryFormParts/PrimaryForm.SyncScreen.cs @@ -5,6 +5,7 @@ namespace DualScreenDemo public partial class PrimaryForm : Form { public Panel primaryScreenPanel; + public Panel videoPanel; public Button syncServiceBellButton; public Button syncCutSongButton; public Button syncReplayButton; @@ -21,6 +22,7 @@ namespace DualScreenDemo private void InitializeSyncScreen() { this.primaryScreenPanel = new System.Windows.Forms.Panel(); + this.videoPanel = new System.Windows.Forms.Panel(); this.syncServiceBellButton = new System.Windows.Forms.Button(); this.syncCutSongButton = 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); this.primaryScreenPanel.TabIndex = 0; - this.primaryScreenPanel.BorderStyle = BorderStyle.FixedSingle; + this.primaryScreenPanel.BorderStyle = BorderStyle.None; 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(); // 同步畫面 服務鈴 @@ -125,6 +131,7 @@ namespace DualScreenDemo this.ClientSize = new System.Drawing.Size(1440, 900); this.Controls.Add(this.primaryScreenPanel); + this.primaryScreenPanel.Controls.Add(this.videoPanel); this.Controls.Add(this.syncCloseButton); this.Name = "PrimaryForm"; this.ResumeLayout(false); diff --git a/PrimaryFormParts/PrimaryForm.cs b/PrimaryFormParts/PrimaryForm.cs index a70bf2d..d181b9c 100644 --- a/PrimaryFormParts/PrimaryForm.cs +++ b/PrimaryFormParts/PrimaryForm.cs @@ -1894,21 +1894,15 @@ namespace DualScreenDemo #region 主畫面按鍵點擊事件 private void MuteUnmuteButton_Click(object sender, EventArgs e) { - if (videoPlayerForm.isMuted) { - - videoPlayerForm.SetVolume(videoPlayerForm.previousVolume); - + videoPlayerForm.Mute(false); videoPlayerForm.isMuted = false; OverlayForm.MainForm.HideMuteLabel(); } else { - - videoPlayerForm.previousVolume = videoPlayerForm.GetVolume(); - videoPlayerForm.SetVolume(-10000); - + videoPlayerForm.Mute(true); videoPlayerForm.isMuted = true; OverlayForm.MainForm.ShowMuteLabel(); } diff --git a/Program.cs b/Program.cs index 1756239..0e80ecd 100644 --- a/Program.cs +++ b/Program.cs @@ -13,7 +13,7 @@ namespace DualScreenDemo private static PrimaryForm primaryForm; // 儲存實例的參考 public static Room room = new Room(); - public static string verSion = "Server V2.8 202508120940"; + public static string verSion = "Server V2.8 202508130945"; [STAThread] static void Main() diff --git a/Services/MediaService.cs b/Services/MediaService.cs index f41fced..4d76216 100644 --- a/Services/MediaService.cs +++ b/Services/MediaService.cs @@ -1,234 +1,201 @@ -using DirectShowLib; -using System; -using System.Runtime.InteropServices; +using LibVLCSharp.Shared; namespace DualScreenDemo.Services { - public class MediaService : Video + public class MediaService : IDisposable { - public IGraphBuilder graphBuilder; - private IMediaControl mediaControl; - public IBaseFilter renderer; - //private IBaseFilter audioRenderer; - private IBaseFilter lavSplitter; - private IVideoWindow videoWindow; - public int Run() => (mediaControl != null) ? mediaControl.Run() : 0; - public int Stop() => (mediaControl != null) ? mediaControl.Stop() : 0; - public int Pause() => (mediaControl != null) ? mediaControl.Pause() : 0; + private readonly LibVLC _libVLC; + private readonly MediaPlayer _mediaPlayerPrimary; + private readonly MediaPlayer _mediaPlayerSecondary; + private Media? _media; + private Media? _mediaNoAudio; + private bool _disposed; - public void VideoPlayerForm_FormClosing() + public MediaService() { - if (videoWindow != null) - { - videoWindow.put_Visible(OABool.False); - videoWindow.put_Owner(IntPtr.Zero); - Marshal.ReleaseComObject(videoWindow); - 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) + { + _mediaPlayerPrimary.Hwnd = handle; + _mediaPlayerPrimary.AspectRatio = $"{width}:{height}"; + _mediaPlayerPrimary.Scale = 0; // 保持原比例 + } + + 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() { - 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; + var duration = _mediaPlayerSecondary.Media?.Duration ?? 0; + var time = _mediaPlayerSecondary.Time; + return duration > 0 && Math.Abs(duration - time) < 1000; } - public void RenderMediaFile(string filePath, nint Handle, int Width, int Height) + + public void LoadMedia(string filePath, int audioTrackIndex = 0) { - if (videoWindow != null) videoWindow.put_Visible(OABool.False); - Stop(); - SafeRelease(ref renderer); - SafeRelease(ref lavSplitter); - SafeRelease(ref graphBuilder); + _media?.Dispose(); - - int hr = 0; - graphBuilder = (IGraphBuilder)new FilterGraph(); - try - { - lavSplitter = AddFilterByClsid(graphBuilder, "LAV Splitter", Clsid.LAVSplitter); - AddFilterByClsid(graphBuilder, "LAV Video Decoder", Clsid.LAVVideoDecoder); - renderer = AddFilterByClsid(graphBuilder, "Secondary Video Renderer", Clsid.VideoRenderer); - //audioRenderer = AddFilterByClsid(graphBuilder, "Default DirectSound Device", Clsid.AudioRenderer); - mediaControl = (IMediaControl)graphBuilder; - } - catch (Exception ex) - { - Console.WriteLine("Error initializing graph builder with second monitor: " + ex.Message); - } - hr = graphBuilder.RenderFile(filePath, null); - if (hr == 0) - SetAudioTrackTo(1); - else - Console.WriteLine("Failed to render secondary file."); - // 绑定视频窗口到副屏幕 - videoWindow = renderer 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); - } - mediaControl.Run(); + // 建立一個完整有聲音的 Media 給 secondary 播放器 + _media = new Media(_libVLC, filePath, FromType.FromPath); + _media.AddOption(":audio-output=directsound"); + _media.AddOption($":audio-track={audioTrackIndex}"); + + // 同時準備給 primary 播放器用的無聲音版本 Media + _mediaNoAudio?.Dispose(); + _mediaNoAudio = new Media(_libVLC, filePath, FromType.FromPath); + _mediaNoAudio.AddOption(":no-audio"); // 關閉聲音輸出 } + + public async Task PlayAsync() + { + if (_media == null || _mediaNoAudio == null) return; + + _mediaPlayerPrimary.Play(_mediaNoAudio); // 播放無聲音版本 + await Task.Delay(100); + _mediaPlayerSecondary.Play(_media); // 播放有聲音版本 + + await SyncPlayersAsync(); + } + public async Task Play() + { + _mediaPlayerPrimary.Play(); // 播放無聲音版本 + await Task.Delay(100); + _mediaPlayerSecondary.Play(); // 播放有聲音版本 + + await SyncPlayersAsync(); + } + + public void Pause() + { + _mediaPlayerPrimary.Pause(); + _mediaPlayerSecondary.Pause(); + } + + 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) { - if (graphBuilder != null) - { - IBasicAudio basicAudio = graphBuilder as IBasicAudio; - if (basicAudio != null) - basicAudio.put_Volume( Math.Max(-10000, Math.Min(0, volume))); - } + _mediaPlayerPrimary.Volume = volume; + _mediaPlayerSecondary.Volume = volume; } - public int GetVolume() + public int GetVolume() => _mediaPlayerSecondary?.Volume ?? 0; + #endregion + + #region Audio Tracks + public List GetAudioTracks() { - if (graphBuilder != null) + var result = new List(); + var media = _mediaPlayerSecondary.Media; + + if (media == null) return result; + + if (!media.IsParsed) + media.Parse(MediaParseOptions.ParseLocal); + + var tracks = media.Tracks; + if (tracks == null) return result; + + foreach (var track in tracks.Where(t => t.TrackType == TrackType.Audio)) { - IBasicAudio basicAudio = graphBuilder as IBasicAudio; - if (basicAudio != null) + result.Add(new TrackDescription { - int volume; - basicAudio.get_Volume(out volume); - return volume; - } + Id = track.Id, + Name = !string.IsNullOrEmpty(track.Language) ? track.Language : $"Audio Track {track.Id}" + }); } - return -10000; + return result; } - private bool isVocalRemoved = false; - public bool ToggleVocalRemoval() + + public async Task SetAudioTrackToAsync(int trackIndex) { - try + var audioTracks = GetAudioTracks(); + if (trackIndex < 0 || trackIndex >= audioTracks.Count) return; + + if (!_mediaPlayerSecondary.IsPlaying) { - 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; - } - - - } - } + _mediaPlayerSecondary.Play(); + await Task.Delay(500); } - catch (Exception ex) - { - Console.WriteLine(ex.Message); - } - return isVocalRemoved; + + _mediaPlayerSecondary.SetAudioTrack(audioTracks[trackIndex].Id); + await Task.Delay(300); } - public void SetAudioTrackTo(int trackIndex) + #endregion + + #region Dispose + public void Dispose() { - try - { - IAMStreamSelect streamSelect = lavSplitter as IAMStreamSelect; + if (_disposed) return; - if (streamSelect != null) - { - int trackCount; - if (streamSelect.Count(out trackCount) == 0 && trackCount > 0) - { - int audioTrackIndex = -1; + _mediaPlayerPrimary?.Dispose(); + _mediaPlayerSecondary?.Dispose(); + _media?.Dispose(); + _mediaNoAudio?.Dispose(); + _libVLC?.Dispose(); - 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); - } + _mediaNoAudio = null; + _media = null; + _disposed = true; } + #endregion + } + + public class TrackDescription + { + public int Id { get; set; } + public string Name { get; set; } = string.Empty; } } \ No newline at end of file diff --git a/Services/MediaServicePrimary.cs b/Services/MediaServicePrimary.cs deleted file mode 100644 index 1fd02a0..0000000 --- a/Services/MediaServicePrimary.cs +++ /dev/null @@ -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); - } - } - - } -} \ No newline at end of file diff --git a/Services/Video.cs b/Services/Video.cs deleted file mode 100644 index 5ffe153..0000000 --- a/Services/Video.cs +++ /dev/null @@ -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(ref T comObject) where T : class - { - if (comObject != null) - { - Marshal.ReleaseComObject(comObject); - comObject = null; - } - } - } -} \ No newline at end of file diff --git a/VideoPlayerForm.cs b/VideoPlayerForm.cs index 27dc21e..dc303a9 100644 --- a/VideoPlayerForm.cs +++ b/VideoPlayerForm.cs @@ -1,7 +1,4 @@ -using System.IO; // For StreamWriter using System.Runtime.InteropServices; -using System.Windows.Media; -using DirectShowLib; using DBObj; using OverlayFormObj; using DualScreenDemo.Services; @@ -69,14 +66,11 @@ namespace DualScreenDemo private const int GWL_EXSTYLE = -20; private const int WS_EX_TOPMOST = 0x00000008; private const uint SWP_NOZORDER = 0x0004; - private MediaServicePrimary primary = new MediaServicePrimary(); - private MediaService secondary = new MediaService(); - - - + //private MediaServicePrimary primary = new MediaServicePrimary(); + private MediaService secondary= new MediaService(); public static OverlayForm overlayForm; public bool isMuted = false; - public int previousVolume = 0; + public int previousVolume = 100; public bool isPaused = false; private bool isSyncToPrimaryMonitor = false; @@ -87,12 +81,9 @@ namespace DualScreenDemo } private static Screen secondMonitor; - private int newWidth; - private int newHeight; public VideoPlayerForm() { - Lold(); Instance = this; // this.DoubleBuffered = true; this.Load += VideoPlayerForm_Load; @@ -103,32 +94,7 @@ namespace DualScreenDemo 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) { @@ -154,14 +120,15 @@ namespace DualScreenDemo } 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(); } } private void VideoPlayerForm_FormClosing(object sender, FormClosingEventArgs e) { - primary.VideoPlayerForm_FormClosing(); - secondary.VideoPlayerForm_FormClosing(); + secondary.Dispose(); // 清理COM CoUninitialize(); } @@ -353,11 +320,11 @@ namespace DualScreenDemo { string pathToPlay = song.getFile(); // 渲染媒體文件 - primary.RenderMediaFile(pathToPlay,PrimaryForm.Instance.primaryScreenPanel.Handle,newWidth,newHeight); - secondary.RenderMediaFile(pathToPlay,this.Handle,secondMonitor.Bounds.Width,secondMonitor.Bounds.Height); + secondary.LoadMedia(pathToPlay,song.isPublicSong ? 0 : 1); + secondary.PlayAsync(); // 音量處理 - SetVolume(isMuted ? -10000 : previousVolume); + SetVolume(isMuted ? 0 : previousVolume); if (isSyncToPrimaryMonitor) SyncToPrimaryMonitor(); return Task.CompletedTask; @@ -389,8 +356,7 @@ namespace DualScreenDemo { try { - bool isAtEnd = secondary.IsAtEnd() ; - if (isAtEnd && !isPaused) + if (secondary.IsAtEnd()) { BeginInvoke(new Action(async () => { @@ -425,21 +391,18 @@ namespace DualScreenDemo public void Play() { - primary.Run(); - secondary.Run(); + secondary.Play(); isPaused = false; OverlayForm.MainForm.HidePauseLabel(); } public void Stop() { - primary.Stop(); secondary.Stop(); } public void Pause() { - primary.Pause(); secondary.Pause(); isPaused = true; OverlayForm.MainForm.ShowPauseLabel(); @@ -477,18 +440,26 @@ namespace DualScreenDemo PrimaryForm.Instance.syncPauseButton.Visible = true; } } + public bool Mute(bool isMuted) + { + return secondary.Mute(isMuted); + } public void SetVolume(int volume) { + Console.WriteLine($"SetVolume: {volume}"); secondary.SetVolume(volume); } public int GetVolume() { return secondary.GetVolume(); } - + private bool isVocalRemoved = false; public void ToggleVocalRemoval() { - OverlayForm.MainForm.ShowTopRightLabelTime(secondary.ToggleVocalRemoval() ? "無人聲" : "有人聲"); + isVocalRemoved=!isVocalRemoved; + int trackIndex = isVocalRemoved ? 1:0; + secondary.SetAudioTrackToAsync(trackIndex); + OverlayForm.MainForm.ShowTopRightLabelTime(isVocalRemoved ? "無人聲" : "有人聲"); } } } diff --git a/superstar.csproj b/superstar.csproj index f97cac6..a9eebda 100644 --- a/superstar.csproj +++ b/superstar.csproj @@ -15,9 +15,12 @@ + + +