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