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); /// /// 获取系统空闲时间(毫秒) /// /// 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; } [DllImport("kernel32.dll")] static extern uint GetTickCount(); /// /// 检测是否有媒体正在播放(视频或音频) /// /// 如果有媒体正在播放返回 true,否则返回 false public static bool IsMediaPlaying() { EnsureMediaPlaybackStatusFresh(); return Volatile.Read(ref _mediaPlaying); } private static bool _mediaPlaying; private static long _mediaLastUpdateMs = -1; private static int _mediaUpdateInProgress; private const int MediaCacheWhenPlayingMs = 500; private const int MediaCacheWhenNotPlayingMs = 1200; private static void EnsureMediaPlaybackStatusFresh() { long now = Environment.TickCount64; long last = Interlocked.Read(ref _mediaLastUpdateMs); int cacheMs = Volatile.Read(ref _mediaPlaying) ? MediaCacheWhenPlayingMs : MediaCacheWhenNotPlayingMs; if (last >= 0 && now - last < cacheMs) { return; } if (Interlocked.Exchange(ref _mediaUpdateInProgress, 1) == 1) { return; } _ = Task.Run(() => { bool playing = false; try { playing = RunOnStaThread(() => { try { return TryIsSystemMediaSessionPlayingAsync().GetAwaiter().GetResult(); } catch { return false; } }); playing = playing || TryIsAudioPlaying(); } catch { playing = false; } finally { Volatile.Write(ref _mediaPlaying, playing); Interlocked.Exchange(ref _mediaLastUpdateMs, Environment.TickCount64); Interlocked.Exchange(ref _mediaUpdateInProgress, 0); } }); } 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; Marshal.ThrowExceptionForHR(deviceEnumerator.GetDefaultAudioEndpoint(EDataFlow.eRender, role, out device)); Guid iid = typeof(IAudioSessionManager2).GUID; Marshal.ThrowExceptionForHR(device.Activate(ref iid, CLSCTX.CLSCTX_ALL, IntPtr.Zero, out sessionManagerObj)); if (sessionManagerObj is null) return false; sessionManager = (IAudioSessionManager2)sessionManagerObj; Marshal.ThrowExceptionForHR(sessionManager.GetSessionEnumerator(out sessionEnumerator)); Marshal.ThrowExceptionForHR(sessionEnumerator.GetCount(out int count)); for (int i = 0; i < count; i++) { Marshal.ThrowExceptionForHR(sessionEnumerator.GetSession(i, out IAudioSessionControl? sessionControl)); if (sessionControl is null) continue; try { Marshal.ThrowExceptionForHR(sessionControl.GetState(out AudioSessionState state)); if (state != AudioSessionState.Active) continue; if (sessionControl is IAudioMeterInformation meter) { Marshal.ThrowExceptionForHR(meter.GetPeakValue(out float peak)); if (peak > 0.0001f) return true; } else { return true; } } finally { Marshal.FinalReleaseComObject(sessionControl); } } return false; } catch { return false; } finally { if (sessionEnumerator is not null) Marshal.FinalReleaseComObject(sessionEnumerator); if (sessionManagerObj is not null) Marshal.FinalReleaseComObject(sessionManagerObj); if (device is not null) Marshal.FinalReleaseComObject(device); if (deviceEnumeratorObj is not null) Marshal.FinalReleaseComObject(deviceEnumeratorObj); } } private static async Task TryIsSystemMediaSessionPlayingAsync() { try { GlobalSystemMediaTransportControlsSessionManager manager = await GlobalSystemMediaTransportControlsSessionManager.RequestAsync(); GlobalSystemMediaTransportControlsSession? current = manager.GetCurrentSession(); if (IsPlaying(current)) { return true; } foreach (GlobalSystemMediaTransportControlsSession session in manager.GetSessions()) { if (IsPlaying(session)) { return true; } } return false; } catch { return false; } } private static bool IsPlaying(GlobalSystemMediaTransportControlsSession? session) { if (session is null) { return false; } try { GlobalSystemMediaTransportControlsSessionPlaybackInfo? info = session.GetPlaybackInfo(); return info is not null && info.PlaybackStatus == GlobalSystemMediaTransportControlsSessionPlaybackStatus.Playing; } catch { return false; } } private static bool RunOnStaThread(Func action) { bool result = false; Exception? error = null; Thread thread = new Thread(() => { try { result = action(); } catch (Exception ex) { error = ex; } }); thread.IsBackground = true; thread.SetApartmentState(ApartmentState.STA); thread.Start(); thread.Join(); if (error is not null) { return false; } return result; } 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(); int GetDefaultAudioEndpoint(EDataFlow dataFlow, ERole role, out IMMDevice ppDevice); } [ComImport] [Guid("D666063F-1587-4E43-81F1-B948E807363F")] [InterfaceType(ComInterfaceType.InterfaceIsIUnknown)] private interface IMMDevice { 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(); int GetSessionEnumerator(out IAudioSessionEnumerator sessionEnum); } [ComImport] [Guid("E2F5BB11-0570-40CA-ACDD-3AA01277DEE8")] [InterfaceType(ComInterfaceType.InterfaceIsIUnknown)] private interface IAudioSessionEnumerator { int GetCount(out int sessionCount); int GetSession(int sessionCount, out IAudioSessionControl session); } [ComImport] [Guid("F4B1A599-7266-4319-A8CA-E70ACB11E8CD")] [InterfaceType(ComInterfaceType.InterfaceIsIUnknown)] private interface IAudioSessionControl { 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 { int GetPeakValue(out float peak); } } }