507 lines
17 KiB
C#
507 lines
17 KiB
C#
using System.Runtime.InteropServices;
|
||
using DBObj;
|
||
using OverlayFormObj;
|
||
using DualScreenDemo.Services;
|
||
namespace DualScreenDemo
|
||
{
|
||
public class VideoPlayerForm : Form
|
||
{
|
||
#region 防止閃屏
|
||
|
||
protected override CreateParams CreateParams
|
||
{
|
||
get
|
||
{
|
||
CreateParams cp = base.CreateParams;
|
||
cp.ExStyle |= 0x02000000;
|
||
return cp;
|
||
}
|
||
}
|
||
#endregion
|
||
|
||
|
||
// 单例实例
|
||
public static VideoPlayerForm Instance { get; private set; }
|
||
|
||
// 导入user32.dll API
|
||
[DllImport("user32.dll")]
|
||
static extern bool SetWindowPos(IntPtr hWnd, IntPtr hWndInsertAfter, int X, int Y, int cx, int cy, uint uFlags);
|
||
|
||
[DllImport("user32.dll", SetLastError = true)]
|
||
static extern IntPtr GetWindowLong(IntPtr hWnd, int nIndex);
|
||
|
||
[DllImport("user32.dll")]
|
||
static extern int SetWindowLong(IntPtr hWnd, int nIndex, IntPtr dwNewLong);
|
||
|
||
// Windows API 函數
|
||
[DllImport("user32.dll")]
|
||
static extern IntPtr GetDesktopWindow();
|
||
|
||
[DllImport("user32.dll")]
|
||
static extern IntPtr MonitorFromWindow(IntPtr hwnd, uint dwFlags);
|
||
|
||
[DllImport("user32.dll")]
|
||
static extern bool GetMonitorInfo(IntPtr hMonitor, ref MONITORINFO lpmi);
|
||
|
||
// MONITORINFO 結構
|
||
[StructLayout(LayoutKind.Sequential)]
|
||
struct MONITORINFO
|
||
{
|
||
public int cbSize;
|
||
public RECT rcMonitor;
|
||
public RECT rcWork;
|
||
public uint dwFlags;
|
||
}
|
||
|
||
// RECT 結構
|
||
[StructLayout(LayoutKind.Sequential)]
|
||
struct RECT
|
||
{
|
||
public int Left;
|
||
public int Top;
|
||
public int Right;
|
||
public int Bottom;
|
||
}
|
||
|
||
private const int GWL_EXSTYLE = -20;
|
||
private const int WS_EX_TOPMOST = 0x00000008;
|
||
private const uint SWP_NOZORDER = 0x0004;
|
||
private MediaService _mediaService0= new MediaService();
|
||
private MediaService _mediaService1= new MediaService();
|
||
|
||
public static OverlayForm overlayForm;
|
||
public bool isMuted = false;
|
||
public int previousVolume = 100;
|
||
public bool isPaused = false;
|
||
private bool isSyncToPrimaryMonitor = false;
|
||
|
||
public bool IsSyncToPrimaryMonitor
|
||
{
|
||
get { return isSyncToPrimaryMonitor; }
|
||
set { isSyncToPrimaryMonitor = value; }
|
||
}
|
||
|
||
private static Screen secondMonitor;
|
||
|
||
public VideoPlayerForm()
|
||
{
|
||
Instance = this;
|
||
// this.DoubleBuffered = true;
|
||
this.Load += VideoPlayerForm_Load;
|
||
this.Shown += VideoPlayerForm_Shown;
|
||
this.FormClosing += VideoPlayerForm_FormClosing;
|
||
InitializeOverlayForm(secondMonitor);
|
||
BringOverlayToFront();
|
||
HttpServer.OnDisplayBarrage += DisplayBarrageOnOverlay;
|
||
MonitorMediaEvents();
|
||
}
|
||
|
||
|
||
private void VideoPlayerForm_Load(object sender, EventArgs e)
|
||
{
|
||
secondMonitor = ScreenHelper.GetSecondMonitor();
|
||
if (secondMonitor != null)
|
||
{
|
||
this.FormBorderStyle = FormBorderStyle.None; // 设置窗体没有边框
|
||
this.StartPosition = FormStartPosition.Manual;
|
||
this.Location = secondMonitor.Bounds.Location;
|
||
this.Size = secondMonitor.Bounds.Size;
|
||
// this.DoubleBuffered = true;
|
||
}
|
||
Screen screen = Screen.FromHandle(this.Handle);
|
||
}
|
||
|
||
private void VideoPlayerForm_Shown(object sender, EventArgs e)
|
||
{
|
||
int hr = CoInitializeEx(IntPtr.Zero, COINIT.APARTMENTTHREADED);
|
||
if (hr < 0)
|
||
{
|
||
Console.WriteLine("Failed to initialize COM library.");
|
||
return;
|
||
}
|
||
else
|
||
{
|
||
_mediaService0.SetVideoOutput(PrimaryForm.Instance.videoPanel.Handle,PrimaryForm.Instance.videoPanel.Width,PrimaryForm.Instance.videoPanel.Height);
|
||
_mediaService1.SetVideoOutput(this.Handle, secondMonitor.Bounds.Width, secondMonitor.Bounds.Height);
|
||
PlayNextSong();
|
||
}
|
||
}
|
||
|
||
private void VideoPlayerForm_FormClosing(object sender, FormClosingEventArgs e)
|
||
{
|
||
_mediaService0.Dispose();
|
||
_mediaService1.Dispose();
|
||
// 清理COM
|
||
CoUninitialize();
|
||
}
|
||
|
||
// COM API函数声明
|
||
[DllImport("ole32.dll")]
|
||
private static extern int CoInitializeEx(IntPtr pvReserved, COINIT dwCoInit);
|
||
|
||
[DllImport("ole32.dll")]
|
||
private static extern void CoUninitialize();
|
||
|
||
// CoInitializeEx() 可以选择的参数
|
||
private enum COINIT : int
|
||
{
|
||
APARTMENTTHREADED = 0x2,
|
||
MULTITHREADED = 0x0
|
||
}
|
||
|
||
// 同步畫面事件
|
||
public void SyncToPrimaryMonitor()
|
||
{
|
||
PrimaryForm.Instance.primaryScreenPanel.Visible = true;
|
||
PrimaryForm.Instance.primaryScreenPanel.BringToFront();
|
||
PrimaryForm.Instance.syncServiceBellButton.Visible = true;
|
||
PrimaryForm.Instance.syncServiceBellButton.BringToFront();
|
||
PrimaryForm.Instance.syncCutSongButton.Visible = true;
|
||
PrimaryForm.Instance.syncCutSongButton.BringToFront();
|
||
PrimaryForm.Instance.syncReplayButton.Visible = true;
|
||
PrimaryForm.Instance.syncReplayButton.BringToFront();
|
||
PrimaryForm.Instance.syncOriginalSongButton.Visible = true;
|
||
PrimaryForm.Instance.syncOriginalSongButton.BringToFront();
|
||
PrimaryForm.Instance.syncMuteButton.Visible = true;
|
||
PrimaryForm.Instance.syncMuteButton.BringToFront();
|
||
if (isPaused)
|
||
{
|
||
PrimaryForm.Instance.syncPlayButton.Visible = true;
|
||
PrimaryForm.Instance.syncPlayButton.BringToFront();
|
||
}
|
||
else
|
||
{
|
||
PrimaryForm.Instance.syncPauseButton.Visible = true;
|
||
PrimaryForm.Instance.syncPauseButton.BringToFront();
|
||
}
|
||
PrimaryForm.Instance.syncVolumeUpButton.Visible = true;
|
||
PrimaryForm.Instance.syncVolumeUpButton.BringToFront();
|
||
PrimaryForm.Instance.syncVolumeDownButton.Visible = true;
|
||
PrimaryForm.Instance.syncVolumeDownButton.BringToFront();
|
||
PrimaryForm.Instance.syncMicUpButton.Visible = true;
|
||
PrimaryForm.Instance.syncMicUpButton.BringToFront();
|
||
PrimaryForm.Instance.syncMicDownButton.Visible = true;
|
||
PrimaryForm.Instance.syncMicDownButton.BringToFront();
|
||
PrimaryForm.Instance.syncCloseButton.Visible = true;
|
||
PrimaryForm.Instance.syncCloseButton.BringToFront();
|
||
|
||
}
|
||
public void ClosePrimaryScreenPanel()
|
||
{
|
||
try
|
||
{
|
||
PrimaryForm.Instance.primaryScreenPanel.Visible = false;
|
||
PrimaryForm.Instance.syncServiceBellButton.Visible = false;
|
||
PrimaryForm.Instance.syncCutSongButton.Visible = false;
|
||
PrimaryForm.Instance.syncReplayButton.Visible = false;
|
||
PrimaryForm.Instance.syncOriginalSongButton.Visible = false;
|
||
PrimaryForm.Instance.syncMuteButton.Visible = false;
|
||
PrimaryForm.Instance.syncPauseButton.Visible = false;
|
||
PrimaryForm.Instance.syncPlayButton.Visible = false;
|
||
PrimaryForm.Instance.syncVolumeUpButton.Visible = false;
|
||
PrimaryForm.Instance.syncVolumeDownButton.Visible = false;
|
||
PrimaryForm.Instance.syncMicUpButton.Visible = false;
|
||
PrimaryForm.Instance.syncMicDownButton.Visible = false;
|
||
PrimaryForm.Instance.syncCloseButton.Visible = false;
|
||
IsSyncToPrimaryMonitor = false;
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
Console.WriteLine(String.Format("Error closing primary screen panel: {0}", ex.Message));
|
||
MessageBox.Show(String.Format("Error closing primary screen panel: {0}", ex.Message));
|
||
}
|
||
}
|
||
|
||
[DllImport("gdi32.dll", ExactSpelling = true)]
|
||
public static extern bool BitBlt(IntPtr hdcDest, int nXDest, int nYDest, int nWidth, int nHeight,
|
||
IntPtr hdcSrc, int nXSrc, int nYSrc, TernaryRasterOperations dwRop);
|
||
|
||
public enum TernaryRasterOperations : uint
|
||
{
|
||
SRCCOPY = 0x00CC0020,
|
||
}
|
||
|
||
|
||
private void DisplayBarrageOnOverlay(string text)
|
||
{
|
||
if (overlayForm.InvokeRequired)
|
||
overlayForm.Invoke(new System.Action(() => overlayForm.DisplayBarrage(text)));
|
||
else
|
||
overlayForm.DisplayBarrage(text);
|
||
}
|
||
|
||
public async Task PlayNextSong()
|
||
{
|
||
// 等待初始化完成(例如播放器、串口等資源尚未就緒時)
|
||
Console.WriteLine("開始播放下一首歌曲...");
|
||
var songToPlay = SongList.Next();
|
||
if (!songToPlay.isPublicSong)
|
||
{
|
||
// 若是使用者點播模式,先送出升Key的串口指令
|
||
if (SerialPortManager.mySerialPort != null && SerialPortManager.mySerialPort.IsOpen)
|
||
{
|
||
byte[] commandBytesIncreasePitch1 = new byte[] { 0xA2, 0x7F, 0xA4 };
|
||
SerialPortManager.mySerialPort.Write(commandBytesIncreasePitch1, 0, commandBytesIncreasePitch1.Length);
|
||
}
|
||
}
|
||
// 更新畫面上顯示的下一首歌資訊
|
||
SongList.UpdateNextSongLabel();
|
||
|
||
// 顯示 QRCode(可能是點歌頁用)
|
||
overlayForm.DisplayQRCodeOnOverlay(HttpServer.randomFolderPath);
|
||
|
||
// 隱藏「暫停中」標籤
|
||
overlayForm.HidePauseLabel();
|
||
|
||
await _InitializeAndPlayMedia(songToPlay);
|
||
|
||
|
||
}
|
||
public async Task ReplayCurrentSong()
|
||
{
|
||
if (Program.room.IsClose())
|
||
{
|
||
return;
|
||
}
|
||
else
|
||
{
|
||
await _InitializeAndPlayMedia(SongList.Current());
|
||
}
|
||
|
||
}
|
||
|
||
private async Task _InitializeAndPlayMedia(SongData song)
|
||
{
|
||
try
|
||
{
|
||
if (InvokeRequired)
|
||
{
|
||
await InvokeAsync(() => InitializeAndPlayMedia(song));
|
||
}
|
||
else
|
||
{
|
||
await InitializeAndPlayMedia(song);
|
||
}
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
Console.WriteLine($"播放時發生錯誤: {ex.Message}");
|
||
await RetryInitializeAndPlayMedia(song);
|
||
}
|
||
}
|
||
|
||
private async Task RetryInitializeAndPlayMedia(SongData song)
|
||
{
|
||
try
|
||
{
|
||
int hr = CoInitializeEx(IntPtr.Zero, COINIT.APARTMENTTHREADED);
|
||
if (hr >= 0)
|
||
{
|
||
await InitializeAndPlayMedia(song);
|
||
}
|
||
else
|
||
{
|
||
Console.WriteLine("CoInitializeEx 失敗,無法重新初始化 COM。");
|
||
}
|
||
}
|
||
catch (Exception retryEx)
|
||
{
|
||
Console.WriteLine($"重試播放時發生錯誤: {retryEx.Message}");
|
||
}
|
||
}
|
||
|
||
// 通用的 async invoke 方法(避免重複寫)
|
||
private Task InvokeAsync(Func<Task> func)
|
||
{
|
||
var tcs = new TaskCompletionSource<bool>();
|
||
BeginInvoke(async () =>
|
||
{
|
||
try
|
||
{
|
||
await func();
|
||
tcs.SetResult(true);
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
tcs.SetException(ex);
|
||
}
|
||
});
|
||
return tcs.Task;
|
||
}
|
||
|
||
private Task InitializeAndPlayMedia(SongData song)
|
||
{
|
||
string pathToPlay = song.getFile();
|
||
_mediaService0.LoadMedia(pathToPlay, 0);
|
||
_mediaService0.Mute(isMuted);
|
||
_mediaService1.LoadMedia(pathToPlay, song.isPublicSong ? 0 : 1);
|
||
_mediaService1.Mute(isMuted);
|
||
|
||
// 音量處理
|
||
//SetVolume(isMuted ? 0 : previousVolume);
|
||
|
||
SetVolume(100+song.getBasic().getDbChange());
|
||
if (isSyncToPrimaryMonitor) SyncToPrimaryMonitor();
|
||
return Task.CompletedTask;
|
||
}
|
||
|
||
private void InitializeOverlayForm(Screen secondaryScreen)
|
||
{
|
||
overlayForm = new OverlayForm();
|
||
Screen secondMonitor = ScreenHelper.GetSecondMonitor();
|
||
if (secondMonitor != null)
|
||
{
|
||
overlayForm.Location = secondMonitor.WorkingArea.Location;
|
||
overlayForm.StartPosition = FormStartPosition.Manual;
|
||
overlayForm.Size = new Size(secondMonitor.WorkingArea.Width, secondMonitor.WorkingArea.Height);
|
||
}
|
||
overlayForm.ShowInTaskbar = false;
|
||
overlayForm.Owner = this;
|
||
overlayForm.Show();
|
||
this.Focus();
|
||
}
|
||
|
||
public void MonitorMediaEvents()
|
||
{
|
||
Task.Run(async () =>
|
||
{
|
||
await Task.Delay(10000);
|
||
Console.WriteLine("開始監聽媒體事件...");
|
||
while (true)
|
||
{
|
||
try
|
||
{
|
||
if (_mediaService0.IsAtEnd() || _mediaService1.IsAtEnd())
|
||
{
|
||
BeginInvoke(new Action(async () =>
|
||
{
|
||
await PlayNextSong();
|
||
}));
|
||
}
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
Console.WriteLine($"監控媒體事件時發生錯誤: {ex.Message}");
|
||
}
|
||
|
||
await Task.Delay(1000);
|
||
}
|
||
});
|
||
}
|
||
|
||
public void BringOverlayToFront()
|
||
{
|
||
if (overlayForm != null)
|
||
{
|
||
if (!overlayForm.Visible)
|
||
{
|
||
overlayForm.Show();
|
||
}
|
||
|
||
overlayForm.BringToFront();
|
||
overlayForm.TopMost = true;
|
||
}
|
||
}
|
||
|
||
public void Play()
|
||
{
|
||
_mediaService0.Play();
|
||
_mediaService1.Play();
|
||
isPaused = false;
|
||
OverlayForm.MainForm.HidePauseLabel();
|
||
}
|
||
|
||
public void Stop()
|
||
{
|
||
_mediaService0.Stop();
|
||
_mediaService1.Stop();
|
||
}
|
||
|
||
public void Pause()
|
||
{
|
||
_mediaService0.Pause();
|
||
_mediaService1.Pause();
|
||
isPaused = true;
|
||
OverlayForm.MainForm.ShowPauseLabel();
|
||
}
|
||
public void PauseOrResumeSong()
|
||
{
|
||
if (isPaused)
|
||
{
|
||
Play();
|
||
PrimaryForm.Instance.pauseButton.Visible = true;
|
||
PrimaryForm.Instance.playButton.Visible = false;
|
||
PrimaryForm.Instance.syncPauseButton.Visible = true;
|
||
PrimaryForm.Instance.syncPlayButton.Visible = false;
|
||
}
|
||
else
|
||
{
|
||
Pause();
|
||
PrimaryForm.Instance.pauseButton.Visible = false;
|
||
PrimaryForm.Instance.playButton.Visible = true;
|
||
PrimaryForm.Instance.syncPauseButton.Visible = false;
|
||
PrimaryForm.Instance.syncPlayButton.Visible = true;
|
||
OverlayForm.MainForm.ShowPauseLabel();
|
||
}
|
||
}
|
||
private void UpdateSyncButtons()
|
||
{
|
||
if (isPaused)
|
||
{
|
||
PrimaryForm.Instance.syncPlayButton.Visible = true;
|
||
PrimaryForm.Instance.syncPauseButton.Visible = false;
|
||
}
|
||
else
|
||
{
|
||
PrimaryForm.Instance.syncPlayButton.Visible = false;
|
||
PrimaryForm.Instance.syncPauseButton.Visible = true;
|
||
}
|
||
}
|
||
public bool Mute(bool isMuted)
|
||
{
|
||
if(isMuted){
|
||
if(isVocalRemoved){
|
||
_mediaService0.Mute(true);
|
||
_mediaService1.Mute(false);
|
||
}else{
|
||
_mediaService0.Mute(false);
|
||
_mediaService1.Mute(true);
|
||
}
|
||
}else{
|
||
_mediaService0.Mute(false);
|
||
_mediaService1.Mute(false);
|
||
}
|
||
|
||
return isMuted;
|
||
}
|
||
public void SetVolume(int volume)
|
||
{
|
||
Console.WriteLine($"SetVolume: {volume}");
|
||
_mediaService0.SetVolume(volume);
|
||
_mediaService1.SetVolume(volume);
|
||
}
|
||
public int GetVolume()
|
||
{
|
||
|
||
return _mediaService1.GetVolume();
|
||
}
|
||
private bool isVocalRemoved = true;
|
||
public void ToggleVocalRemoval()
|
||
{
|
||
isVocalRemoved=!isVocalRemoved;
|
||
if(isVocalRemoved){
|
||
_mediaService0.Mute(true);
|
||
_mediaService1.Mute(false);
|
||
}else{
|
||
_mediaService0.Mute(false);
|
||
_mediaService1.Mute(true);
|
||
}
|
||
//int trackIndex = isVocalRemoved ? 1:0;
|
||
//_mediaService.SetAudioTrack(trackIndex);
|
||
OverlayForm.MainForm.ShowTopRightLabelTime(isVocalRemoved ? "無人聲" : "有人聲");
|
||
}
|
||
}
|
||
}
|
||
|
||
|