Files
TimerApp/NativeMethods.cs
2026-01-18 20:33:46 +08:00

363 lines
12 KiB
C#
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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);
/// <summary>
/// 获取系统空闲时间(毫秒)
/// </summary>
/// <returns></returns>
public static long GetIdleTime()
{
LASTINPUTINFO lastInputInfo = new LASTINPUTINFO
{
cbSize = (uint)Marshal.SizeOf<LASTINPUTINFO>(),
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();
/// <summary>
/// 检测是否有媒体正在播放(视频或音频)
/// </summary>
/// <returns>如果有媒体正在播放返回 true否则返回 false</returns>
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<bool> 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<bool> 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);
}
}
}