363 lines
12 KiB
C#
363 lines
12 KiB
C#
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);
|
||
}
|
||
}
|
||
}
|