using System;
using System.Runtime.InteropServices;
using System.Threading;
using System.Threading.Tasks;
using Windows.Media.Control;
namespace TimerApp
{
public static class NativeMethods
{
[StructLayout(LayoutKind.Sequential)]
struct LASTINPUTINFO
{
public static readonly int SizeOf = Marshal.SizeOf(typeof(LASTINPUTINFO));
[MarshalAs(UnmanagedType.U4)]
public UInt32 cbSize;
[MarshalAs(UnmanagedType.U4)]
public UInt32 dwTime;
}
[DllImport("user32.dll")]
static extern bool GetLastInputInfo(ref LASTINPUTINFO plii);
[DllImport("kernel32.dll")]
static extern uint GetTickCount();
///
/// 获取系统空闲时间(毫秒)
///
///
public static long GetIdleTime()
{
LASTINPUTINFO lastInputInfo = new LASTINPUTINFO
{
cbSize = (uint)Marshal.SizeOf(),
dwTime = 0
};
if (GetLastInputInfo(ref lastInputInfo))
{
uint tickCount = GetTickCount();
uint idleMs = unchecked(tickCount - lastInputInfo.dwTime);
return idleMs;
}
return 0;
}
// ==========================================
// Media Detection Logic (Refactored)
// ==========================================
private static volatile bool _isMediaPlayingCaught;
private static Thread? _mediaMonitorThread;
private static readonly CancellationTokenSource _cts = new CancellationTokenSource();
private static readonly AutoResetEvent _checkSignal = new AutoResetEvent(false);
static NativeMethods()
{
// 在静态构造函数中启动后台监控线程
StartMediaMonitor();
}
///
/// 检测是否有媒体正在播放(视频或音频)
/// 直接返回缓存的状态,无阻塞
///
public static bool IsMediaPlaying()
{
return _isMediaPlayingCaught;
}
///
/// 强制立即进行一次检测
///
public static void InvalidateMediaCache()
{
// 唤醒后台线程立即检测
_checkSignal.Set();
}
///
/// 应用退出时清理资源 (可选调用)
///
public static void Shutdown()
{
_cts.Cancel();
_checkSignal.Set(); // Wake up to exit
}
private static void StartMediaMonitor()
{
if (_mediaMonitorThread != null) return;
_mediaMonitorThread = new Thread(MediaMonitorLoop)
{
IsBackground = true,
Name = "TimerApp_MediaMonitor",
Priority = ThreadPriority.BelowNormal
};
_mediaMonitorThread.SetApartmentState(ApartmentState.STA); // COM requirement
_mediaMonitorThread.Start();
}
private static void MediaMonitorLoop()
{
while (!_cts.IsCancellationRequested)
{
try
{
bool isPlaying = false;
// 1. Check System Media Transport Controls (WinRT)
// 需要在 STA 线程中小心处理 Task
try
{
// 使用 Task.Run 确保在一个干净的上下文执行异步任务,然后同步等待结果
// 注意:因为我们本身就在 STA 线程,直接 .Result 可能会有风险,
// 但对于 GSMTC API,只要没有 SynchronizationContext 绑定到此线程通常是安全的。
// 这里为了稳妥,我们捕获任何异常。
isPlaying = CheckSystemMediaTransportControls();
}
catch
{
// Ignored
}
// 2. Check Legacy Audio Session API (COM)
if (!isPlaying)
{
try
{
isPlaying = TryIsAudioPlaying();
}
catch
{
// Ignored
}
}
// Update volatile cache
_isMediaPlayingCaught = isPlaying;
// Wait for next check (default 1s, or wake up immediately on Invalidate)
_checkSignal.WaitOne(1000);
}
catch
{
// Prevent thread crash
Thread.Sleep(1000);
}
}
}
private static bool CheckSystemMediaTransportControls()
{
// 同步等待异步方法,因为我们在专用线程上,且没有 UI SyncContext,这是安全的
return CheckSystemMediaTransportControlsAsync().GetAwaiter().GetResult();
}
private static async Task CheckSystemMediaTransportControlsAsync()
{
try
{
// RequestAsync 可能在某些系统上较慢
var manager = await GlobalSystemMediaTransportControlsSessionManager.RequestAsync();
var current = manager.GetCurrentSession();
if (IsPlaying(current)) return true;
foreach (var session in manager.GetSessions())
{
if (IsPlaying(session)) return true;
}
return false;
}
catch
{
return false;
}
}
private static bool IsPlaying(GlobalSystemMediaTransportControlsSession? session)
{
if (session == null) return false;
try
{
var info = session.GetPlaybackInfo();
return info != null && info.PlaybackStatus == GlobalSystemMediaTransportControlsSessionPlaybackStatus.Playing;
}
catch
{
return false;
}
}
// ==========================================
// COM Audio Detection
// ==========================================
private static bool TryIsAudioPlaying()
{
return TryIsAudioPlaying(ERole.eMultimedia) ||
TryIsAudioPlaying(ERole.eConsole) ||
TryIsAudioPlaying(ERole.eCommunications);
}
private static bool TryIsAudioPlaying(ERole role)
{
object? deviceEnumeratorObj = null;
IMMDeviceEnumerator? deviceEnumerator = null;
IMMDevice? device = null;
object? sessionManagerObj = null;
IAudioSessionManager2? sessionManager = null;
IAudioSessionEnumerator? sessionEnumerator = null;
try
{
Type? enumeratorType = Type.GetTypeFromCLSID(CLSID_MMDeviceEnumerator);
if (enumeratorType is null) return false;
deviceEnumeratorObj = Activator.CreateInstance(enumeratorType);
if (deviceEnumeratorObj is null) return false;
deviceEnumerator = (IMMDeviceEnumerator)deviceEnumeratorObj;
// GetDefaultAudioEndpoint can fail if no audio device is present
int hr = deviceEnumerator.GetDefaultAudioEndpoint(EDataFlow.eRender, role, out device);
if (hr != 0) return false; // S_OK = 0
Guid iid = typeof(IAudioSessionManager2).GUID;
hr = device.Activate(ref iid, CLSCTX.CLSCTX_ALL, IntPtr.Zero, out sessionManagerObj);
if (hr != 0 || sessionManagerObj is null) return false;
sessionManager = (IAudioSessionManager2)sessionManagerObj;
hr = sessionManager.GetSessionEnumerator(out sessionEnumerator);
if (hr != 0) return false;
hr = sessionEnumerator.GetCount(out int count);
if (hr != 0) return false;
for (int i = 0; i < count; i++)
{
IAudioSessionControl? sessionControl = null;
try
{
hr = sessionEnumerator.GetSession(i, out sessionControl);
if (hr != 0 || sessionControl is null) continue;
hr = sessionControl.GetState(out AudioSessionState state);
if (hr != 0) continue;
if (state == AudioSessionState.Active)
{
if (sessionControl is IAudioMeterInformation meter)
{
hr = meter.GetPeakValue(out float peak);
if (hr == 0 && peak > 0.000001f) // Slightly relaxed threshold
return true;
}
else
{
// Cannot check peak, assume active means playing
return true;
}
}
}
finally
{
if (sessionControl != null) Marshal.FinalReleaseComObject(sessionControl);
}
}
return false;
}
catch
{
return false;
}
finally
{
if (sessionEnumerator != null) Marshal.FinalReleaseComObject(sessionEnumerator);
if (sessionManager != null) Marshal.FinalReleaseComObject(sessionManager); // Corrected variable usage
if (sessionManagerObj != null) Marshal.FinalReleaseComObject(sessionManagerObj);
if (device != null) Marshal.FinalReleaseComObject(device);
if (deviceEnumeratorObj != null) Marshal.FinalReleaseComObject(deviceEnumeratorObj);
}
}
private static readonly Guid CLSID_MMDeviceEnumerator = new Guid("BCDE0395-E52F-467C-8E3D-C4579291692E");
private enum EDataFlow
{
eRender = 0,
eCapture = 1,
eAll = 2
}
private enum ERole
{
eConsole = 0,
eMultimedia = 1,
eCommunications = 2
}
private enum AudioSessionState
{
Inactive = 0,
Active = 1,
Expired = 2
}
[Flags]
private enum CLSCTX : uint
{
CLSCTX_INPROC_SERVER = 0x1,
CLSCTX_INPROC_HANDLER = 0x2,
CLSCTX_LOCAL_SERVER = 0x4,
CLSCTX_REMOTE_SERVER = 0x10,
CLSCTX_ALL = CLSCTX_INPROC_SERVER | CLSCTX_INPROC_HANDLER | CLSCTX_LOCAL_SERVER | CLSCTX_REMOTE_SERVER
}
[ComImport]
[Guid("A95664D2-9614-4F35-A746-DE8DB63617E6")]
[InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
private interface IMMDeviceEnumerator
{
int NotImpl1();
[PreserveSig]
int GetDefaultAudioEndpoint(EDataFlow dataFlow, ERole role, out IMMDevice ppDevice);
}
[ComImport]
[Guid("D666063F-1587-4E43-81F1-B948E807363F")]
[InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
private interface IMMDevice
{
[PreserveSig]
int Activate(ref Guid iid, CLSCTX dwClsCtx, IntPtr pActivationParams, [MarshalAs(UnmanagedType.IUnknown)] out object ppInterface);
}
[ComImport]
[Guid("77AA99A0-1BD6-484F-8BC7-2C654C9A9B6F")]
[InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
private interface IAudioSessionManager2
{
int NotImpl1();
int NotImpl2();
[PreserveSig]
int GetSessionEnumerator(out IAudioSessionEnumerator sessionEnum);
}
[ComImport]
[Guid("E2F5BB11-0570-40CA-ACDD-3AA01277DEE8")]
[InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
private interface IAudioSessionEnumerator
{
[PreserveSig]
int GetCount(out int sessionCount);
[PreserveSig]
int GetSession(int sessionCount, out IAudioSessionControl session);
}
[ComImport]
[Guid("F4B1A599-7266-4319-A8CA-E70ACB11E8CD")]
[InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
private interface IAudioSessionControl
{
[PreserveSig]
int GetState(out AudioSessionState state);
int NotImpl1();
int NotImpl2();
int NotImpl3();
int NotImpl4();
int NotImpl5();
int NotImpl6();
int NotImpl7();
int NotImpl8();
}
[ComImport]
[Guid("C02216F6-8C67-4B5B-9D00-D008E73E0064")]
[InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
private interface IAudioMeterInformation
{
[PreserveSig]
int GetPeakValue(out float peak);
}
}
}