Delphi 如何停止系统音乐?(VK_MEDIA_停止在任何地方工作)

Delphi 如何停止系统音乐?(VK_MEDIA_停止在任何地方工作),delphi,winapi,Delphi,Winapi,我正在开发一个在后台运行的应用程序,当特定事件发生时,媒体播放器播放的音乐应该停止 Spotify和Windows Media Player等主要音乐播放器支持通过特殊键盘键“停止”、“开始”、“下一首曲目”等进行控制 因此,我想使用此功能来控制所有正在运行的媒体播放器,并告诉它们停止播放 未实现这些密钥的媒体播放器不会成为我的应用程序的目标;它们将是不相容的 目前,我有以下功能,到目前为止都可以使用: procedure StopMusic; const KEYEVENTF_KEYDOWN

我正在开发一个在后台运行的应用程序,当特定事件发生时,媒体播放器播放的音乐应该停止

Spotify和Windows Media Player等主要音乐播放器支持通过特殊键盘键“停止”、“开始”、“下一首曲目”等进行控制

因此,我想使用此功能来控制所有正在运行的媒体播放器,并告诉它们停止播放

未实现这些密钥的媒体播放器不会成为我的应用程序的目标;它们将是不相容的

目前,我有以下功能,到目前为止都可以使用:

procedure StopMusic;
const
  KEYEVENTF_KEYDOWN = 0;
  KEYEVENTF_KEYUP = 2;
begin
  keybd_event(VK_MEDIA_STOP, 0, KEYEVENTF_KEYDOWN, 0);
  keybd_event(VK_MEDIA_STOP, 0, KEYEVENTF_KEYUP, 0);
end;
我不知道这个机制是如何运作的。操作系统会向所有应用程序发送消息,无论我当前在哪个应用程序中工作,但只有在调用keybd_事件时才会发送消息。(SendMessage WM_KEYDOWN不起作用)

我遇到的问题是,一些像OwnCloud这样的应用程序在窗口聚焦时会吞下这些键(如果在真正的键盘上按下)。我会联系他们报告这个错误。但与此同时,我想试着为它做一个变通办法

我的解决方法是快速将焦点切换到不同的窗口,然后执行虚拟按键,然后切换回激活的窗口:

procedure StopMusic;
const
  KEYEVENTF_KEYDOWN = 0;
  KEYEVENTF_KEYUP = 2;
var
  hBak: HWND;
begin
  hBak := GetForegroundWindow();
  try
    SetForegroundWindow(GetDesktopWindow());
    keybd_event(VK_MEDIA_STOP, 0, KEYEVENTF_KEYDOWN, 0);
    keybd_event(VK_MEDIA_STOP, 0, KEYEVENTF_KEYUP, 0);
  finally
    SetForegroundWindow(hBak);
  end;
end;
但这个功能根本不起作用。无论当前聚焦哪个窗口,它都没有效果。我猜这是因为桌面无法获得焦点

因此,我的问题是:

  • 我可以做哪些变通方法?(除了要求OwnCloud修复该漏洞)
  • 或者是否有完全不同的策略/API来告诉媒体播放器停止播放
更新:解决方案1(脏)

第一个解决方法是将焦点切换到任务栏。但显然,这是一个肮脏的解决方案,因为它是Windows的一个实现细节

procedure StopMusic;
const
  KEYEVENTF_KEYDOWN = 0;
  KEYEVENTF_KEYUP = 2;
var
  hBak: HWND;
begin
  hBak := GetForegroundWindow();
  try
    SetForegroundWindow(FindWindow('Shell_TrayWnd', nil));
    keybd_event(VK_MEDIA_STOP, 0, KEYEVENTF_KEYDOWN, 0);
    keybd_event(VK_MEDIA_STOP, 0, KEYEVENTF_KEYUP, 0);
  finally
    SetForegroundWindow(hBak);
  end;
end;
更新:解决方案2(脏)

这个解决方案也有效。它不像Shell_TrayWnd那样是硬编码字符串,但仍然使用未记录的API调用

function TaskmanWindow: HWND;
type
  TGetTaskmanWindow = function(): HWND; stdcall;
var
  hUser32: THandle;
  GetTaskmanWindow: TGetTaskmanWindow;
begin
  Result := 0;
  hUser32 := GetModuleHandle('user32.dll');
  if (hUser32 > 0) then
  begin
    @GetTaskmanWindow := GetProcAddress(hUser32, 'GetTaskmanWindow'); // Undocumented
    if Assigned(GetTaskmanWindow) then
    begin
      Result := GetTaskmanWindow;
    end;
  end;
end;

procedure StopMusic;
const
  KEYEVENTF_KEYDOWN = 0;
  KEYEVENTF_KEYUP = 2;
var
  hBak: HWND;
begin
  hBak := GetForegroundWindow();
  try
    SetForegroundWindow(TaskmanWindow);
    keybd_event(VK_MEDIA_STOP, 0, KEYEVENTF_KEYDOWN, 0);
    keybd_event(VK_MEDIA_STOP, 0, KEYEVENTF_KEYUP, 0);
  finally
    SetForegroundWindow(hBak);
  end;
end;
更新:解决方案3(良好)

问题的干净解决方案是向所有应用程序发送AppCommand

procedure StopMusic;
begin
  SendMessage(HWND_BROADCAST, WM_APPCOMMAND, 0, MAKELONG(0, APPCOMMAND_MEDIA_STOP));
end;
更新:Bugreport

一个拥有github帐户的朋友为我发了帖子

... keybd_事件(VK_媒体停止,0,KEYEVENTF_键关闭,0); keybd_事件(VK_媒体停止,0,KEYEVENTF_KEYUP,0); ... 我不知道这个机制是怎么运作的


此代码模拟按下
VK\u MEDIA\u STOP
键,通过
APPCOMMAND\u MEDIA\u STOP
应用程序命令生成
WM\u APPCOMMAND
消息。可以从消息中了解接下来会发生什么:

如果子窗口不处理此消息,而是调用 DefWindowProcDefWindowProc将向其父级发送消息 窗户。如果顶层窗口未处理此消息,则 相反,调用DefWindowProcDefWindowProc将使用 钩子代码等于HSHELL\u APPCOMMAND

因此,当按下键或模拟输入时,应用程序的窗口位于前台时,如果聚焦控件或其父链未停止消息处理,并且顶层窗口调用默认窗口过程,则会生成shell事件。你提到的表现良好的媒体播放器是那些必须接收shell hook消息的播放器。本MSDN中也解释了这一机制

OwnCloud所做的显然是在接收到
WM_APPCOMMAND
后不调用默认窗口过程。当它在前台时,这恰好会引起你的问题;其他媒体应用程序无法响应该消息,因为未生成外壳事件

因此,您可以尝试激活非干扰窗口并生成该窗口的键盘输入。这是毫无意义的,您自己的应用程序窗口与任务栏或任何其他应用程序窗口一样好,只要您不处理消息。当您的应用程序窗口位于前台,并且您生成输入时,如果Windows Media Player没有响应,则会出现其他问题,我们需要一个复制案例


在任何情况下,生成shell事件都不会对OwnCloud或类似的应用程序有所帮助,因为它们没有用于shell钩子通知的注册窗口。对于这些应用程序,您可以自己发送
WM_APPCOMMAND
。链接的MSDN博客文章有这样一个例子。虽然不建议您这样做,但如果您有可能运行无限数量的媒体应用程序,也可以广播此消息。有关不建议这样做的一些注意事项,请参阅本MSDN。如果您没有看到广播消息的任何问题,请考虑使用<代码> sEndoMasaTeMeOut<<代码>,而不是<代码> SeNeMedis< /C> >。如果它在前台时不响应真实键盘,则不会生成任何输入。我不明白,一个程序的任意窗口如何阻止另一个程序上播放的媒体?摆弄前台窗口没有任何意义。你在前台的应用很好。阅读
WM_APPCOMMAND
文档中的备注。如果应用程序没有吞下该键,默认窗口过程将生成一个shell钩子事件(
HSHELL\u APPCOMMAND
),感兴趣的应用程序(
registerSharelHookWindow
)随后可以响应。不感兴趣的应用程序将一事无成。@DanielMarschall:像Windows Media Player和Spotify这样的应用程序是专门编码来识别键盘的媒体键的,而像ownCloud这样的应用程序显然不是。这是一个可选功能。你什么都不做 ... keybd_event(VK_MEDIA_STOP, 0, KEYEVENTF_KEYDOWN, 0); keybd_event(VK_MEDIA_STOP, 0, KEYEVENTF_KEYUP, 0); ...