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)
{
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()));
}

View File

@ -120,7 +120,9 @@ namespace DBObj
db.Field<string>("artistA_simplified") ?? "",
db.Field<string>("artistB_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 FileName;
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;
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 int getDbChange() => DbChange;
public string getSituation() => Situation;
public string setNameTest(string s) => Name = Name + s;
}
}

View File

@ -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;

View File

@ -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();

View File

@ -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()
));
}
}

View File

@ -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);

View File

@ -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();
}

View File

@ -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()

View File

@ -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)
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);
videoWindow.put_Owner(IntPtr.Zero);
Marshal.ReleaseComObject(videoWindow);
videoWindow = null;
_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;
var duration = _mediaPlayerSecondary.Media?.Duration ?? 0;
var time = _mediaPlayerSecondary.Time;
return duration > 0 && Math.Abs(duration - time) < 1000;
}
// 添加更严格的结束条件判断
isAtEnd = durationSeconds > 0 && currentSeconds > 0 &&
Math.Abs(currentSeconds - durationSeconds) < 0.1; // 确保真的到了结尾
//Console.WriteLine($"檢測到歌曲 - 當前位置: {currentSeconds:F2}秒, 總時長: {durationSeconds:F2}秒");
if (isAtEnd)
public void LoadMedia(string filePath, int audioTrackIndex = 0)
{
Console.WriteLine($"檢測到歌曲 -結束: {currentSeconds:F2}秒, 總時長: {durationSeconds:F2}秒");
}
}
}
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?.Dispose();
// 建立一個完整有聲音的 Media 給 secondary 播放器
_media = new Media(_libVLC, filePath, FromType.FromPath);
_media.AddOption(":audio-output=directsound");
_media.AddOption($":audio-track={audioTrackIndex}");
int hr = 0;
graphBuilder = (IGraphBuilder)new FilterGraph();
try
// 同時準備給 primary 播放器用的無聲音版本 Media
_mediaNoAudio?.Dispose();
_mediaNoAudio = new Media(_libVLC, filePath, FromType.FromPath);
_mediaNoAudio.AddOption(":no-audio"); // 關閉聲音輸出
}
public async Task PlayAsync()
{
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;
if (_media == null || _mediaNoAudio == null) return;
_mediaPlayerPrimary.Play(_mediaNoAudio); // 播放無聲音版本
await Task.Delay(100);
_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)
SetAudioTrackTo(1);
else
Console.WriteLine("Failed to render secondary file.");
// 绑定视频窗口到副屏幕
videoWindow = renderer as IVideoWindow;
if (videoWindow != null)
public void Pause()
{
videoWindow.put_Owner(Handle);
videoWindow.put_WindowStyle(WindowStyle.Child | WindowStyle.ClipChildren | WindowStyle.ClipSiblings);
videoWindow.SetWindowPosition(0, 0, Width, Height);
videoWindow.put_Visible(OABool.True);
_mediaPlayerPrimary.Pause();
_mediaPlayerSecondary.Pause();
}
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)
{
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()
{
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;
public int GetVolume() => _mediaPlayerSecondary?.Volume ?? 0;
#endregion
if (streamSelect != null)
#region Audio Tracks
public List<TrackDescription> GetAudioTracks()
{
int trackCount;
if (streamSelect.Count(out trackCount) == 0 && trackCount > 0)
{
int currentTrackIndex = -1;
int audioTrack1 = -1;
int audioTrack2 = -1;
var result = new List<TrackDescription>();
var media = _mediaPlayerSecondary.Media;
for (int i = 0; i < trackCount; i++)
{
// 獲取音軌信息
AMMediaType mediaType;
AMStreamSelectInfoFlags flags;
int lcid, dwGroup;
string name;
object pObject, pUnk;
if (media == null) return result;
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)
{
audioTrack2 = i;
return result;
}
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
}
// 切換音軌
if (currentTrackIndex == audioTrack1 && audioTrack2 != -1)
public class TrackDescription
{
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);
}
}
public int Id { get; set; }
public string Name { get; set; } = string.Empty;
}
}

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.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 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 ? "無人聲" : "有人聲");
}
}
}

View File

@ -15,9 +15,12 @@
</PropertyGroup>
<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="System.Data.SQLite.Core" Version="1.0.117" />
<PackageReference Include="System.IO.Ports" Version="9.0.3" />
<PackageReference Include="VideoLAN.LibVLC.Windows" Version="3.0.21" />
</ItemGroup>
<ItemGroup>
<Reference Include="AxInterop.WMPLib">