feat: 重构媒体播放检测逻辑,使用后台STA线程持续监控并集成WinRT API。
This commit is contained in:
@@ -124,29 +124,54 @@ namespace TimerApp
|
|||||||
|
|
||||||
private void OnTick()
|
private void OnTick()
|
||||||
{
|
{
|
||||||
|
// 在锁外执行耗时的系统检测
|
||||||
|
long idleMs = 0;
|
||||||
|
bool? newMediaPlaying = null;
|
||||||
|
|
||||||
lock (_lock)
|
lock (_lock)
|
||||||
{
|
{
|
||||||
// 如果处于暂停状态,不处理计时逻辑
|
// 如果处于暂停状态,不处理计时逻辑
|
||||||
if (_isPaused)
|
if (_isPaused) return;
|
||||||
{
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
long idleMs;
|
// 检查是否需要更新状态
|
||||||
TimeSpan idleTime;
|
|
||||||
|
|
||||||
// 优化:降低系统API调用频率
|
|
||||||
// 每 CheckIntervalTicks (3) 秒更新一次状态
|
|
||||||
_checkTickCounter++;
|
_checkTickCounter++;
|
||||||
if (_checkTickCounter >= CheckIntervalTicks)
|
if (_checkTickCounter >= CheckIntervalTicks)
|
||||||
{
|
{
|
||||||
_checkTickCounter = 0;
|
_checkTickCounter = 0;
|
||||||
_cachedIdleMs = NativeMethods.GetIdleTime();
|
// 需要更新,但在锁外进行
|
||||||
_cachedMediaPlaying = NativeMethods.IsMediaPlaying();
|
newMediaPlaying = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 如果不需要更新,直接使用缓存值
|
||||||
|
if (newMediaPlaying == null)
|
||||||
|
{
|
||||||
idleMs = _cachedIdleMs;
|
idleMs = _cachedIdleMs;
|
||||||
idleTime = TimeSpan.FromMilliseconds(idleMs);
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 在锁外执行实际的检测
|
||||||
|
if (newMediaPlaying == true)
|
||||||
|
{
|
||||||
|
idleMs = NativeMethods.GetIdleTime();
|
||||||
|
bool playing = NativeMethods.IsMediaPlaying();
|
||||||
|
|
||||||
|
// 更新缓存
|
||||||
|
lock (_lock)
|
||||||
|
{
|
||||||
|
_cachedIdleMs = idleMs;
|
||||||
|
_cachedMediaPlaying = playing;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
lock (_lock)
|
||||||
|
{
|
||||||
|
// 再次检查暂停状态
|
||||||
|
if (_isPaused) return;
|
||||||
|
|
||||||
|
// 使用(可能是新更新的)缓存值
|
||||||
|
idleMs = _cachedIdleMs;
|
||||||
|
bool isMediaPlaying = _cachedMediaPlaying;
|
||||||
|
TimeSpan idleTime = TimeSpan.FromMilliseconds(idleMs);
|
||||||
|
|
||||||
if (CurrentState == MonitorState.Resting)
|
if (CurrentState == MonitorState.Resting)
|
||||||
{
|
{
|
||||||
@@ -170,9 +195,6 @@ namespace TimerApp
|
|||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
// 使用缓存的媒体播放状态
|
|
||||||
bool isMediaPlaying = _cachedMediaPlaying;
|
|
||||||
|
|
||||||
// 工作/空闲模式逻辑
|
// 工作/空闲模式逻辑
|
||||||
if (idleTime > IdleThreshold || isMediaPlaying)
|
if (idleTime > IdleThreshold || isMediaPlaying)
|
||||||
{
|
{
|
||||||
|
|||||||
309
NativeMethods.cs
309
NativeMethods.cs
@@ -22,6 +22,9 @@ namespace TimerApp
|
|||||||
[DllImport("user32.dll")]
|
[DllImport("user32.dll")]
|
||||||
static extern bool GetLastInputInfo(ref LASTINPUTINFO plii);
|
static extern bool GetLastInputInfo(ref LASTINPUTINFO plii);
|
||||||
|
|
||||||
|
[DllImport("kernel32.dll")]
|
||||||
|
static extern uint GetTickCount();
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 获取系统空闲时间(毫秒)
|
/// 获取系统空闲时间(毫秒)
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@@ -44,87 +47,164 @@ namespace TimerApp
|
|||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
[DllImport("kernel32.dll")]
|
// ==========================================
|
||||||
static extern uint GetTickCount();
|
// 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();
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 检测是否有媒体正在播放(视频或音频)
|
/// 检测是否有媒体正在播放(视频或音频)
|
||||||
|
/// 直接返回缓存的状态,无阻塞
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <returns>如果有媒体正在播放返回 true,否则返回 false</returns>
|
|
||||||
public static bool IsMediaPlaying()
|
public static bool IsMediaPlaying()
|
||||||
{
|
{
|
||||||
EnsureMediaPlaybackStatusFresh();
|
return _isMediaPlayingCaught;
|
||||||
return Volatile.Read(ref _mediaPlaying);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 强制失效媒体播放状态缓存
|
/// 强制立即进行一次检测
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public static void InvalidateMediaCache()
|
public static void InvalidateMediaCache()
|
||||||
{
|
{
|
||||||
Interlocked.Exchange(ref _mediaLastUpdateMs, -1);
|
// 唤醒后台线程立即检测
|
||||||
// 不直接重置 _mediaPlaying,以免在检测过程中出现闪烁,
|
_checkSignal.Set();
|
||||||
// 只是强制下一次 IsMediaPlaying 触发新的检测。
|
|
||||||
// 但如果处于 Resume 状态,假设不播放是安全的。
|
|
||||||
Volatile.Write(ref _mediaPlaying, false);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private static bool _mediaPlaying;
|
/// <summary>
|
||||||
private static long _mediaLastUpdateMs = -1;
|
/// 应用退出时清理资源 (可选调用)
|
||||||
private static int _mediaUpdateInProgress;
|
/// </summary>
|
||||||
private const int MediaCacheWhenPlayingMs = 500;
|
public static void Shutdown()
|
||||||
private const int MediaCacheWhenNotPlayingMs = 1200;
|
|
||||||
|
|
||||||
private static void EnsureMediaPlaybackStatusFresh()
|
|
||||||
{
|
{
|
||||||
long now = Environment.TickCount64;
|
_cts.Cancel();
|
||||||
long last = Interlocked.Read(ref _mediaLastUpdateMs);
|
_checkSignal.Set(); // Wake up to exit
|
||||||
int cacheMs = Volatile.Read(ref _mediaPlaying) ? MediaCacheWhenPlayingMs : MediaCacheWhenNotPlayingMs;
|
|
||||||
if (last >= 0 && now - last < cacheMs)
|
|
||||||
{
|
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (Interlocked.Exchange(ref _mediaUpdateInProgress, 1) == 1)
|
private static void StartMediaMonitor()
|
||||||
{
|
{
|
||||||
return;
|
if (_mediaMonitorThread != null) return;
|
||||||
|
|
||||||
|
_mediaMonitorThread = new Thread(MediaMonitorLoop)
|
||||||
|
{
|
||||||
|
IsBackground = true,
|
||||||
|
Name = "TimerApp_MediaMonitor",
|
||||||
|
Priority = ThreadPriority.BelowNormal
|
||||||
|
};
|
||||||
|
_mediaMonitorThread.SetApartmentState(ApartmentState.STA); // COM requirement
|
||||||
|
_mediaMonitorThread.Start();
|
||||||
}
|
}
|
||||||
|
|
||||||
_ = Task.Run(() =>
|
private static void MediaMonitorLoop()
|
||||||
{
|
{
|
||||||
bool playing = false;
|
while (!_cts.IsCancellationRequested)
|
||||||
try
|
|
||||||
{
|
|
||||||
playing = RunOnStaThread(() =>
|
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
return TryIsSystemMediaSessionPlayingAsync().GetAwaiter().GetResult();
|
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<bool> 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
|
catch
|
||||||
{
|
{
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
});
|
}
|
||||||
|
|
||||||
playing = playing || TryIsAudioPlaying();
|
private static bool IsPlaying(GlobalSystemMediaTransportControlsSession? session)
|
||||||
|
{
|
||||||
|
if (session == null) return false;
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var info = session.GetPlaybackInfo();
|
||||||
|
return info != null && info.PlaybackStatus == GlobalSystemMediaTransportControlsSessionPlaybackStatus.Playing;
|
||||||
}
|
}
|
||||||
catch
|
catch
|
||||||
{
|
{
|
||||||
playing = false;
|
return false;
|
||||||
}
|
}
|
||||||
finally
|
|
||||||
{
|
|
||||||
Volatile.Write(ref _mediaPlaying, playing);
|
|
||||||
Interlocked.Exchange(ref _mediaLastUpdateMs, Environment.TickCount64);
|
|
||||||
Interlocked.Exchange(ref _mediaUpdateInProgress, 0);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ==========================================
|
||||||
|
// COM Audio Detection
|
||||||
|
// ==========================================
|
||||||
|
|
||||||
private static bool TryIsAudioPlaying()
|
private static bool TryIsAudioPlaying()
|
||||||
{
|
{
|
||||||
return TryIsAudioPlaying(ERole.eMultimedia) || TryIsAudioPlaying(ERole.eConsole) || TryIsAudioPlaying(ERole.eCommunications);
|
return TryIsAudioPlaying(ERole.eMultimedia) ||
|
||||||
|
TryIsAudioPlaying(ERole.eConsole) ||
|
||||||
|
TryIsAudioPlaying(ERole.eCommunications);
|
||||||
}
|
}
|
||||||
|
|
||||||
private static bool TryIsAudioPlaying(ERole role)
|
private static bool TryIsAudioPlaying(ERole role)
|
||||||
@@ -139,51 +219,57 @@ namespace TimerApp
|
|||||||
try
|
try
|
||||||
{
|
{
|
||||||
Type? enumeratorType = Type.GetTypeFromCLSID(CLSID_MMDeviceEnumerator);
|
Type? enumeratorType = Type.GetTypeFromCLSID(CLSID_MMDeviceEnumerator);
|
||||||
if (enumeratorType is null)
|
if (enumeratorType is null) return false;
|
||||||
return false;
|
|
||||||
|
|
||||||
deviceEnumeratorObj = Activator.CreateInstance(enumeratorType);
|
deviceEnumeratorObj = Activator.CreateInstance(enumeratorType);
|
||||||
if (deviceEnumeratorObj is null)
|
if (deviceEnumeratorObj is null) return false;
|
||||||
return false;
|
|
||||||
deviceEnumerator = (IMMDeviceEnumerator)deviceEnumeratorObj;
|
deviceEnumerator = (IMMDeviceEnumerator)deviceEnumeratorObj;
|
||||||
|
|
||||||
Marshal.ThrowExceptionForHR(deviceEnumerator.GetDefaultAudioEndpoint(EDataFlow.eRender, role, out device));
|
// 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;
|
Guid iid = typeof(IAudioSessionManager2).GUID;
|
||||||
Marshal.ThrowExceptionForHR(device.Activate(ref iid, CLSCTX.CLSCTX_ALL, IntPtr.Zero, out sessionManagerObj));
|
hr = device.Activate(ref iid, CLSCTX.CLSCTX_ALL, IntPtr.Zero, out sessionManagerObj);
|
||||||
if (sessionManagerObj is null)
|
if (hr != 0 || sessionManagerObj is null) return false;
|
||||||
return false;
|
|
||||||
sessionManager = (IAudioSessionManager2)sessionManagerObj;
|
sessionManager = (IAudioSessionManager2)sessionManagerObj;
|
||||||
|
|
||||||
Marshal.ThrowExceptionForHR(sessionManager.GetSessionEnumerator(out sessionEnumerator));
|
hr = sessionManager.GetSessionEnumerator(out sessionEnumerator);
|
||||||
Marshal.ThrowExceptionForHR(sessionEnumerator.GetCount(out int count));
|
if (hr != 0) return false;
|
||||||
|
|
||||||
|
hr = sessionEnumerator.GetCount(out int count);
|
||||||
|
if (hr != 0) return false;
|
||||||
|
|
||||||
for (int i = 0; i < count; i++)
|
for (int i = 0; i < count; i++)
|
||||||
{
|
{
|
||||||
Marshal.ThrowExceptionForHR(sessionEnumerator.GetSession(i, out IAudioSessionControl? sessionControl));
|
IAudioSessionControl? sessionControl = null;
|
||||||
if (sessionControl is null)
|
|
||||||
continue;
|
|
||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
Marshal.ThrowExceptionForHR(sessionControl.GetState(out AudioSessionState state));
|
hr = sessionEnumerator.GetSession(i, out sessionControl);
|
||||||
if (state != AudioSessionState.Active)
|
if (hr != 0 || sessionControl is null) continue;
|
||||||
continue;
|
|
||||||
|
|
||||||
|
hr = sessionControl.GetState(out AudioSessionState state);
|
||||||
|
if (hr != 0) continue;
|
||||||
|
|
||||||
|
if (state == AudioSessionState.Active)
|
||||||
|
{
|
||||||
if (sessionControl is IAudioMeterInformation meter)
|
if (sessionControl is IAudioMeterInformation meter)
|
||||||
{
|
{
|
||||||
Marshal.ThrowExceptionForHR(meter.GetPeakValue(out float peak));
|
hr = meter.GetPeakValue(out float peak);
|
||||||
if (peak > 0.0001f)
|
if (hr == 0 && peak > 0.000001f) // Slightly relaxed threshold
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
|
// Cannot check peak, assume active means playing
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
finally
|
finally
|
||||||
{
|
{
|
||||||
Marshal.FinalReleaseComObject(sessionControl);
|
if (sessionControl != null) Marshal.FinalReleaseComObject(sessionControl);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -195,94 +281,14 @@ namespace TimerApp
|
|||||||
}
|
}
|
||||||
finally
|
finally
|
||||||
{
|
{
|
||||||
if (sessionEnumerator is not null) Marshal.FinalReleaseComObject(sessionEnumerator);
|
if (sessionEnumerator != null) Marshal.FinalReleaseComObject(sessionEnumerator);
|
||||||
if (sessionManagerObj is not null) Marshal.FinalReleaseComObject(sessionManagerObj);
|
if (sessionManager != null) Marshal.FinalReleaseComObject(sessionManager); // Corrected variable usage
|
||||||
if (device is not null) Marshal.FinalReleaseComObject(device);
|
if (sessionManagerObj != null) Marshal.FinalReleaseComObject(sessionManagerObj);
|
||||||
if (deviceEnumeratorObj is not null) Marshal.FinalReleaseComObject(deviceEnumeratorObj);
|
if (device != null) Marshal.FinalReleaseComObject(device);
|
||||||
|
if (deviceEnumeratorObj != 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();
|
|
||||||
|
|
||||||
// Wait with timeout (e.g. 2 seconds) to prevent hanging
|
|
||||||
if (!thread.Join(2000))
|
|
||||||
{
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (error is not null)
|
|
||||||
{
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
private static readonly Guid CLSID_MMDeviceEnumerator = new Guid("BCDE0395-E52F-467C-8E3D-C4579291692E");
|
private static readonly Guid CLSID_MMDeviceEnumerator = new Guid("BCDE0395-E52F-467C-8E3D-C4579291692E");
|
||||||
|
|
||||||
private enum EDataFlow
|
private enum EDataFlow
|
||||||
@@ -322,6 +328,7 @@ namespace TimerApp
|
|||||||
private interface IMMDeviceEnumerator
|
private interface IMMDeviceEnumerator
|
||||||
{
|
{
|
||||||
int NotImpl1();
|
int NotImpl1();
|
||||||
|
[PreserveSig]
|
||||||
int GetDefaultAudioEndpoint(EDataFlow dataFlow, ERole role, out IMMDevice ppDevice);
|
int GetDefaultAudioEndpoint(EDataFlow dataFlow, ERole role, out IMMDevice ppDevice);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -330,6 +337,7 @@ namespace TimerApp
|
|||||||
[InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
|
[InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
|
||||||
private interface IMMDevice
|
private interface IMMDevice
|
||||||
{
|
{
|
||||||
|
[PreserveSig]
|
||||||
int Activate(ref Guid iid, CLSCTX dwClsCtx, IntPtr pActivationParams, [MarshalAs(UnmanagedType.IUnknown)] out object ppInterface);
|
int Activate(ref Guid iid, CLSCTX dwClsCtx, IntPtr pActivationParams, [MarshalAs(UnmanagedType.IUnknown)] out object ppInterface);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -340,6 +348,7 @@ namespace TimerApp
|
|||||||
{
|
{
|
||||||
int NotImpl1();
|
int NotImpl1();
|
||||||
int NotImpl2();
|
int NotImpl2();
|
||||||
|
[PreserveSig]
|
||||||
int GetSessionEnumerator(out IAudioSessionEnumerator sessionEnum);
|
int GetSessionEnumerator(out IAudioSessionEnumerator sessionEnum);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -348,7 +357,9 @@ namespace TimerApp
|
|||||||
[InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
|
[InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
|
||||||
private interface IAudioSessionEnumerator
|
private interface IAudioSessionEnumerator
|
||||||
{
|
{
|
||||||
|
[PreserveSig]
|
||||||
int GetCount(out int sessionCount);
|
int GetCount(out int sessionCount);
|
||||||
|
[PreserveSig]
|
||||||
int GetSession(int sessionCount, out IAudioSessionControl session);
|
int GetSession(int sessionCount, out IAudioSessionControl session);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -357,6 +368,7 @@ namespace TimerApp
|
|||||||
[InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
|
[InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
|
||||||
private interface IAudioSessionControl
|
private interface IAudioSessionControl
|
||||||
{
|
{
|
||||||
|
[PreserveSig]
|
||||||
int GetState(out AudioSessionState state);
|
int GetState(out AudioSessionState state);
|
||||||
int NotImpl1();
|
int NotImpl1();
|
||||||
int NotImpl2();
|
int NotImpl2();
|
||||||
@@ -373,6 +385,7 @@ namespace TimerApp
|
|||||||
[InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
|
[InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
|
||||||
private interface IAudioMeterInformation
|
private interface IAudioMeterInformation
|
||||||
{
|
{
|
||||||
|
[PreserveSig]
|
||||||
int GetPeakValue(out float peak);
|
int GetPeakValue(out float peak);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user