superstar_v2/VideoPlayerForm.cs

505 lines
17 KiB
C#
Raw Normal View History

2025-04-07 16:54:10 +08:00
using System.IO; // For StreamWriter
using System.Runtime.InteropServices;
using System.Windows.Media;
2025-04-07 16:54:10 +08:00
using DirectShowLib;
using DBObj;
using OverlayFormObj;
using DualScreenDemo.Services;
2025-04-07 16:54:10 +08:00
namespace DualScreenDemo
{
public class VideoPlayerForm : Form
{
#region
2025-04-07 16:54:10 +08:00
protected override CreateParams CreateParams
{
get
{
CreateParams cp = base.CreateParams;
cp.ExStyle |= 0x02000000;
return cp;
}
}
2025-06-20 13:12:10 +08:00
#endregion
2025-04-07 16:54:10 +08:00
// 单例实例
public static VideoPlayerForm Instance { get; private set; }
2025-06-20 13:12:10 +08:00
2025-04-07 16:54:10 +08:00
// 导入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 MediaServicePrimary primary = new MediaServicePrimary();
private MediaService secondary = new MediaService();
2025-04-07 16:54:10 +08:00
public static OverlayForm overlayForm;
public bool isMuted = false;
public int previousVolume = 0;
2025-04-07 16:54:10 +08:00
public bool isPaused = false;
private bool isSyncToPrimaryMonitor = false;
public bool IsSyncToPrimaryMonitor
{
get { return isSyncToPrimaryMonitor; }
set { isSyncToPrimaryMonitor = value; }
}
private static Screen secondMonitor;
private int newWidth;
private int newHeight;
2025-04-07 16:54:10 +08:00
public VideoPlayerForm()
{
Lold();
2025-04-07 16:54:10 +08:00
Instance = this;
// this.DoubleBuffered = true;
this.Load += VideoPlayerForm_Load;
this.Shown += VideoPlayerForm_Shown;
this.FormClosing += VideoPlayerForm_FormClosing;
InitializeOverlayForm(secondMonitor);
BringOverlayToFront();
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);
}
}
2025-04-07 16:54:10 +08:00
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);
2025-04-07 16:54:10 +08:00
}
2025-07-21 18:36:09 +08:00
2025-04-07 16:54:10 +08:00
private void VideoPlayerForm_Shown(object sender, EventArgs e)
{
int hr = CoInitializeEx(IntPtr.Zero, COINIT.APARTMENTTHREADED);
if (hr < 0)
{
2025-04-07 16:54:10 +08:00
Console.WriteLine("Failed to initialize COM library.");
return;
}
else
{
PlayNextSong();
}
2025-04-07 16:54:10 +08:00
}
private void VideoPlayerForm_FormClosing(object sender, FormClosingEventArgs e)
{
primary.VideoPlayerFormClosing();
secondary.VideoPlayerFormClosing();
2025-04-07 16:54:10 +08:00
// 清理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
}
2025-05-08 16:49:32 +08:00
// 同步畫面事件
2025-04-07 16:54:10 +08:00
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();
2025-05-08 16:49:32 +08:00
2025-04-07 16:54:10 +08:00
}
2025-06-20 13:12:10 +08:00
public void ClosePrimaryScreenPanel()
2025-04-07 16:54:10 +08:00
{
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()
{
// 等待初始化完成(例如播放器、串口等資源尚未就緒時)
2025-04-07 16:54:10 +08:00
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);
}
2025-04-07 16:54:10 +08:00
}
2025-06-20 13:12:10 +08:00
// pathToPlay 需要調整
2025-07-08 18:13:36 +08:00
var pathToPlay = songToPlay.getFile();
2025-06-20 13:12:10 +08:00
// 更新畫面上顯示的下一首歌資訊
SongList.UpdateNextSongLabel();
2025-06-20 13:12:10 +08:00
// 顯示 QRCode可能是點歌頁用
overlayForm.DisplayQRCodeOnOverlay(HttpServer.randomFolderPath);
2025-06-20 13:12:10 +08:00
// 隱藏「暫停中」標籤
overlayForm.HidePauseLabel();
await _InitializeAndPlayMedia(pathToPlay);
2025-04-07 16:54:10 +08:00
}
public async Task ReplayCurrentSong()
2025-04-07 16:54:10 +08:00
{
var songToPlay = SongList.Current();
2025-07-08 18:13:36 +08:00
var pathToPlay = songToPlay.getFile();
2025-04-07 16:54:10 +08:00
// UpdateMarqueeTextForCurrentSong(songToPlay);
await _InitializeAndPlayMedia(pathToPlay);
}
private async Task _InitializeAndPlayMedia(string pathToPlay)
{
2025-04-07 16:54:10 +08:00
try
{
if (InvokeRequired)
2025-04-07 16:54:10 +08:00
{
await InvokeAsync(() => InitializeAndPlayMedia(pathToPlay));
}
else
{
await InitializeAndPlayMedia(pathToPlay);
2025-04-07 16:54:10 +08:00
}
}
catch (Exception ex)
{
Console.WriteLine($"播放時發生錯誤: {ex.Message}");
await RetryInitializeAndPlayMedia(pathToPlay);
2025-04-07 16:54:10 +08:00
}
}
private async Task RetryInitializeAndPlayMedia(string path)
2025-04-07 16:54:10 +08:00
{
try
{
int hr = CoInitializeEx(IntPtr.Zero, COINIT.APARTMENTTHREADED);
if (hr >= 0)
2025-04-07 16:54:10 +08:00
{
await InitializeAndPlayMedia(path);
2025-04-07 16:54:10 +08:00
}
else
2025-04-07 16:54:10 +08:00
{
Console.WriteLine("CoInitializeEx 失敗,無法重新初始化 COM。");
2025-04-07 16:54:10 +08:00
}
}
catch (Exception retryEx)
2025-04-07 16:54:10 +08:00
{
Console.WriteLine($"重試播放時發生錯誤: {retryEx.Message}");
2025-04-07 16:54:10 +08:00
}
}
// 通用的 async invoke 方法(避免重複寫)
private Task InvokeAsync(Func<Task> func)
2025-04-07 16:54:10 +08:00
{
var tcs = new TaskCompletionSource<bool>();
BeginInvoke(async () =>
2025-04-07 16:54:10 +08:00
{
try
{
await func();
tcs.SetResult(true);
}
catch (Exception ex)
{
tcs.SetException(ex);
}
});
return tcs.Task;
2025-04-07 16:54:10 +08:00
}
private Task InitializeAndPlayMedia(string pathToPlay)
2025-04-07 16:54:10 +08:00
{
// 渲染媒體文件
primary.RenderMediaFile(pathToPlay,PrimaryForm.Instance.primaryScreenPanel.Handle,newWidth,newHeight);
secondary.RenderMediaFile(pathToPlay,this.Handle,secondMonitor.Bounds.Width,secondMonitor.Bounds.Height);
2025-04-07 16:54:10 +08:00
// 音量處理
SetVolume(isMuted ? -10000 : previousVolume);
// 開始播放
primary.Run();
secondary.Run();
if (isSyncToPrimaryMonitor) SyncToPrimaryMonitor();
return Task.CompletedTask;
2025-04-07 16:54:10 +08:00
}
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 () =>
{
Console.WriteLine("開始監聽媒體事件...");
2025-04-07 16:54:10 +08:00
while (true)
{
try
{
bool isAtEnd = secondary.isAtEnd() ;
if (isAtEnd && !isPaused)
2025-04-07 16:54:10 +08:00
{
BeginInvoke(new Action(async () =>
2025-04-07 16:54:10 +08:00
{
await PlayNextSong();
}));
2025-04-07 16:54:10 +08:00
}
}
catch (Exception ex)
{
Console.WriteLine($"監控媒體事件時發生錯誤: {ex.Message}");
await Task.Delay(1000);
}
await Task.Delay(1000);
2025-04-07 16:54:10 +08:00
}
});
}
public void BringOverlayToFront()
{
if (overlayForm != null)
{
if (!overlayForm.Visible)
{
overlayForm.Show();
}
overlayForm.BringToFront();
overlayForm.TopMost = true;
}
}
public void Play()
{
primary.Run();
secondary.Run();
2025-04-07 16:54:10 +08:00
isPaused = false;
OverlayForm.MainForm.HidePauseLabel();
}
public void Stop()
{
primary.Stop();
secondary.Stop();
2025-04-07 16:54:10 +08:00
}
public void Pause()
{
primary.Pause();
secondary.Pause();
2025-04-07 16:54:10 +08:00
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 void SetVolume(int volume)
{
secondary.SetVolume(volume);
2025-04-07 16:54:10 +08:00
}
public int GetVolume()
{
return secondary.GetVolume();
2025-04-07 16:54:10 +08:00
}
public void ToggleVocalRemoval()
2025-04-07 16:54:10 +08:00
{
OverlayForm.MainForm.ShowTopRightLabelTime(secondary.ToggleVocalRemoval() ? "無人聲" : "有人聲");
2025-04-07 16:54:10 +08:00
}
}
2025-06-20 13:12:10 +08:00
}