202508151141
調整 VLC 雙螢幕同步顯示
This commit is contained in:
parent
32eeaa0a83
commit
4d94dc6d8e
@ -5,142 +5,67 @@ namespace DualScreenDemo.Services
|
|||||||
public class MediaService : IDisposable
|
public class MediaService : IDisposable
|
||||||
{
|
{
|
||||||
private readonly LibVLC _libVLC;
|
private readonly LibVLC _libVLC;
|
||||||
private readonly MediaPlayer _mediaPlayerPrimary;
|
private readonly MediaPlayer _mediaPlayer;
|
||||||
private readonly MediaPlayer _mediaPlayerSecondary;
|
|
||||||
private Media? _media;
|
private Media? _media;
|
||||||
private Media? _mediaNoAudio;
|
|
||||||
private bool _disposed;
|
private bool _disposed;
|
||||||
|
|
||||||
public MediaService()
|
public MediaService(nint handle)
|
||||||
{
|
{
|
||||||
Core.Initialize();
|
Core.Initialize();
|
||||||
_libVLC = new LibVLC(
|
_libVLC = new LibVLC(
|
||||||
"--aout=directsound",
|
|
||||||
//"--avcodec-hw=dxva2",
|
|
||||||
"--network-caching=300",
|
"--network-caching=300",
|
||||||
"--file-caching=300",
|
"--file-caching=300",
|
||||||
"--audio-time-stretch"
|
"--audio-time-stretch",
|
||||||
|
"--video-filter=clone",
|
||||||
|
$"--clone-views={handle}" // 這是 clone 的目標視窗
|
||||||
);
|
);
|
||||||
|
|
||||||
_mediaPlayerPrimary = new MediaPlayer(_libVLC);
|
_mediaPlayer = new MediaPlayer(_libVLC);
|
||||||
_mediaPlayerSecondary = new MediaPlayer(_libVLC);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#region Player Setup
|
#region Player Setup
|
||||||
public void SetVideoFormPrimary(nint handle, int width, int height)
|
public void SetVideoOutput(nint handle, int width, int height)
|
||||||
{
|
{
|
||||||
_mediaPlayerPrimary.Hwnd = handle;
|
_mediaPlayer.Hwnd = handle;
|
||||||
_mediaPlayerPrimary.AspectRatio = $"{width}:{height}";
|
_mediaPlayer.AspectRatio = $"{width}:{height}";
|
||||||
_mediaPlayerPrimary.Scale = 0; // 保持原比例
|
_mediaPlayer.Scale = 0;
|
||||||
}
|
|
||||||
|
|
||||||
public void SetVideoFormSecondary(nint handle, int width, int height)
|
|
||||||
{
|
|
||||||
_mediaPlayerSecondary.Hwnd = handle;
|
|
||||||
_mediaPlayerSecondary.AspectRatio = $"{width}:{height}";
|
|
||||||
_mediaPlayerSecondary.Scale = 0;
|
|
||||||
}
|
}
|
||||||
#endregion
|
#endregion
|
||||||
|
|
||||||
#region Playback
|
#region Playback
|
||||||
public MediaPlayer PrimaryPlayer => _mediaPlayerPrimary;
|
public MediaPlayer Player => _mediaPlayer;
|
||||||
public MediaPlayer SecondaryPlayer => _mediaPlayerSecondary;
|
public bool IsPlaying => _mediaPlayer.IsPlaying;
|
||||||
public bool IsPlaying => _mediaPlayerSecondary.IsPlaying;
|
|
||||||
|
|
||||||
public bool IsAtEnd()
|
public bool IsAtEnd()
|
||||||
{
|
{
|
||||||
var duration = _mediaPlayerSecondary.Media?.Duration ?? 0;
|
var duration = _mediaPlayer.Media?.Duration ?? 0;
|
||||||
var time = _mediaPlayerSecondary.Time;
|
var time = _mediaPlayer.Time;
|
||||||
return duration > 0 && Math.Abs(duration - time) < 1000;
|
return duration > 0 && Math.Abs(duration - time) < 1000;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void LoadMedia(string filePath, int audioTrackIndex = 0)
|
public void LoadMedia(string filePath, int audioTrackIndex = 0)
|
||||||
{
|
{
|
||||||
_mediaPlayerPrimary.Pause();
|
_mediaPlayer.Stop();
|
||||||
_mediaPlayerSecondary.Pause();
|
|
||||||
_media?.Dispose();
|
_media?.Dispose();
|
||||||
|
|
||||||
// 建立一個完整有聲音的 Media 給 secondary 播放器
|
|
||||||
_media = new Media(_libVLC, filePath, FromType.FromPath);
|
_media = new Media(_libVLC, filePath, FromType.FromPath);
|
||||||
_media.AddOption(":audio-output=directsound");
|
|
||||||
_media.AddOption($":audio-track={audioTrackIndex}");
|
_media.AddOption($":audio-track={audioTrackIndex}");
|
||||||
|
|
||||||
// 同時準備給 primary 播放器用的無聲音版本 Media
|
|
||||||
_mediaNoAudio?.Dispose();
|
|
||||||
_mediaNoAudio = new Media(_libVLC, filePath, FromType.FromPath);
|
|
||||||
_mediaNoAudio.AddOption(":no-audio"); // 關閉聲音輸出
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task PlayAsync()
|
public void Play()
|
||||||
{
|
{
|
||||||
if (_media == null || _mediaNoAudio == null) return;
|
if (_media == null) return;
|
||||||
|
_mediaPlayer.Play(_media);
|
||||||
_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();
|
|
||||||
}
|
}
|
||||||
|
public void Pause() => _mediaPlayer.Pause();
|
||||||
|
public void Stop() => _mediaPlayer.Stop();
|
||||||
#endregion
|
#endregion
|
||||||
|
|
||||||
#region Sync
|
#region Audio
|
||||||
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)
|
|
||||||
{
|
|
||||||
_mediaPlayerPrimary.Volume = volume;
|
|
||||||
_mediaPlayerSecondary.Volume = volume;
|
|
||||||
}
|
|
||||||
public int GetVolume() => _mediaPlayerSecondary?.Volume ?? 0;
|
|
||||||
#endregion
|
|
||||||
|
|
||||||
#region Audio Tracks
|
|
||||||
public List<TrackDescription> GetAudioTracks()
|
public List<TrackDescription> GetAudioTracks()
|
||||||
{
|
{
|
||||||
var result = new List<TrackDescription>();
|
var result = new List<TrackDescription>();
|
||||||
var media = _mediaPlayerSecondary.Media;
|
var media = _mediaPlayer.Media;
|
||||||
|
|
||||||
if (media == null) return result;
|
if (media == null) return result;
|
||||||
|
|
||||||
@ -161,26 +86,29 @@ namespace DualScreenDemo.Services
|
|||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void SetAudioTrackToAsync(int trackIndex)
|
public void SetAudioTrack(int trackIndex)
|
||||||
{
|
{
|
||||||
var audioTracks = GetAudioTracks();
|
var audioTracks = GetAudioTracks();
|
||||||
if (trackIndex < 0 || trackIndex >= audioTracks.Count) return;
|
if (trackIndex < 0 || trackIndex >= audioTracks.Count) return;
|
||||||
_mediaPlayerSecondary.SetAudioTrack(audioTracks[trackIndex].Id);
|
_mediaPlayer.SetAudioTrack(audioTracks[trackIndex].Id);
|
||||||
}
|
}
|
||||||
#endregion
|
#endregion
|
||||||
|
|
||||||
|
#region Volume
|
||||||
|
public void SetVolume(int volume) => _mediaPlayer.Volume = volume;
|
||||||
|
public int GetVolume() => _mediaPlayer.Volume;
|
||||||
|
public bool Mute(bool isMuted) => _mediaPlayer.Mute = isMuted;
|
||||||
|
#endregion
|
||||||
|
|
||||||
#region Dispose
|
#region Dispose
|
||||||
public void Dispose()
|
public void Dispose()
|
||||||
{
|
{
|
||||||
if (_disposed) return;
|
if (_disposed) return;
|
||||||
|
|
||||||
_mediaPlayerPrimary?.Dispose();
|
_mediaPlayer.Dispose();
|
||||||
_mediaPlayerSecondary?.Dispose();
|
|
||||||
_media?.Dispose();
|
_media?.Dispose();
|
||||||
_mediaNoAudio?.Dispose();
|
_libVLC.Dispose();
|
||||||
_libVLC?.Dispose();
|
|
||||||
|
|
||||||
_mediaNoAudio = null;
|
|
||||||
_media = null;
|
_media = null;
|
||||||
_disposed = true;
|
_disposed = true;
|
||||||
}
|
}
|
||||||
|
@ -67,7 +67,7 @@ namespace DualScreenDemo
|
|||||||
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;
|
||||||
public static OverlayForm overlayForm;
|
public static OverlayForm overlayForm;
|
||||||
public bool isMuted = false;
|
public bool isMuted = false;
|
||||||
public int previousVolume = 100;
|
public int previousVolume = 100;
|
||||||
@ -119,9 +119,10 @@ namespace DualScreenDemo
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
secondary.SetVideoFormPrimary(PrimaryForm.Instance.videoPanel.Handle,PrimaryForm.Instance.videoPanel.Width,PrimaryForm.Instance.videoPanel.Height);
|
secondary= new MediaService(PrimaryForm.Instance.videoPanel.Handle);
|
||||||
secondary.SetVideoFormSecondary(this.Handle,secondMonitor.Bounds.Width,secondMonitor.Bounds.Height);
|
secondary.SetVideoOutput(this.Handle, secondMonitor.Bounds.Width, secondMonitor.Bounds.Height);
|
||||||
|
|
||||||
PlayNextSong();
|
PlayNextSong();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -322,7 +323,7 @@ namespace DualScreenDemo
|
|||||||
// 渲染媒體文件
|
// 渲染媒體文件
|
||||||
secondary.LoadMedia(pathToPlay,song.isPublicSong ? 0 : 1);
|
secondary.LoadMedia(pathToPlay,song.isPublicSong ? 0 : 1);
|
||||||
secondary.Mute(isMuted);
|
secondary.Mute(isMuted);
|
||||||
secondary.PlayAsync();
|
secondary.Play();
|
||||||
// 音量處理
|
// 音量處理
|
||||||
//SetVolume(isMuted ? 0 : previousVolume);
|
//SetVolume(isMuted ? 0 : previousVolume);
|
||||||
|
|
||||||
@ -459,7 +460,7 @@ namespace DualScreenDemo
|
|||||||
{
|
{
|
||||||
isVocalRemoved=!isVocalRemoved;
|
isVocalRemoved=!isVocalRemoved;
|
||||||
int trackIndex = isVocalRemoved ? 1:0;
|
int trackIndex = isVocalRemoved ? 1:0;
|
||||||
secondary.SetAudioTrackToAsync(trackIndex);
|
secondary.SetAudioTrack(trackIndex);
|
||||||
OverlayForm.MainForm.ShowTopRightLabelTime(isVocalRemoved ? "無人聲" : "有人聲");
|
OverlayForm.MainForm.ShowTopRightLabelTime(isVocalRemoved ? "無人聲" : "有人聲");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -9,6 +9,7 @@
|
|||||||
<ImplicitUsings>enable</ImplicitUsings>
|
<ImplicitUsings>enable</ImplicitUsings>
|
||||||
<Nullable>enable</Nullable>
|
<Nullable>enable</Nullable>
|
||||||
<UseWPF>true</UseWPF>
|
<UseWPF>true</UseWPF>
|
||||||
|
<EnableWindowsTargeting>true</EnableWindowsTargeting>
|
||||||
<ApplicationManifest>app.manifest</ApplicationManifest> <!-- 確保這一行引用了 manifest 文件 -->
|
<ApplicationManifest>app.manifest</ApplicationManifest> <!-- 確保這一行引用了 manifest 文件 -->
|
||||||
<UseWindowsForms>true</UseWindowsForms>
|
<UseWindowsForms>true</UseWindowsForms>
|
||||||
<NoWarn>CS8618,CS8602,CS8622,CS8625,CS8600,CS8603,CS8601,CS8604,CS4014</NoWarn>
|
<NoWarn>CS8618,CS8602,CS8622,CS8625,CS8600,CS8603,CS8601,CS8604,CS4014</NoWarn>
|
||||||
|
Loading…
x
Reference in New Issue
Block a user