2017-01-06 3 views
2

Я разрабатываю приложение, которое работает в фоновом режиме, и когда происходит конкретное событие, музыка, которую играют медиаплееры, должна быть остановлена.Как остановить музыку в системе? (VK_MEDIA_STOP не работает повсюду)

основных музыкальных плееров, таких как 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_event. (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; 

Но эта функция не работает вообще. Он не имеет никакого эффекта, независимо от того, какое Окно в настоящее время сосредоточено. Я думаю, это потому, что Desktop не может получить фокус.

Поэтому мой вопрос:

  • Какой обходной путь я могу сделать? (Помимо запроса OwnCloud исправить эту ошибку)
  • Или существует ли совершенно другая стратегия/API, позволяющая медиаплеерам перестать играть?

UPDATE: Решение 1 (Dirty)

Первый обходной путь, чтобы переключить фокус на панель задач. Но, очевидно, это грязное решение, поскольку это деталь реализации 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; 

UPDATE: Решение 2 (Dirty)

Это решение также работает. Это не жесткая цепочка, такая как 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; 

UPDATE: Решение 3 (Хорошо)

Чистое решение проблемы заключается в том, чтобы отправить AppCommand ко всем приложениям

procedure StopMusic; 
begin 
    SendMessage(HWND_BROADCAST, WM_APPCOMMAND, 0, MAKELONG(0, APPCOMMAND_MEDIA_STOP)); 
end; 

UPDATE: отчет об ошибке

Друг с учетной записью github отправил the bugreport для меня.

+0

AutoHotKey выполнит это. Однако никогда не используйте keybd_event. Прочтите его документацию, чтобы узнать, почему нет. –

+1

Это по усмотрению приложения, следует ли соблюдать ключи или нет. Если он не отвечает на настоящую клавиатуру, когда она находится на переднем плане, это не приведет к ее созданию. –

+0

@SertacAkyuz Да; поэтому я хотел переключиться на окно, где я знаю, что он принимает ключи. –

ответ

2
 
... 
keybd_event(VK_MEDIA_STOP, 0, KEYEVENTF_KEYDOWN, 0); 
keybd_event(VK_MEDIA_STOP, 0, KEYEVENTF_KEYUP, 0); 
...

Я не знаю точно, как работает этот механизм ...


Этот код имитирует нажатие VK_MEDIA_STOP ключа, который генерирует WM_APPCOMMAND сообщений с командой APPCOMMAND_MEDIA_STOP приложения. Это можно узнать, что происходит, то из данного сообщения documentation:

Если дочернее окно не обрабатывает это сообщение, а вместо этого вызывает DefWindowProc, DefWindowProc будет отправить сообщение своего окно родительского . Если окно верхнего уровня не обрабатывает это сообщение и вместо вызовов DefWindowProc, DefWindowProc будет вызывать крюк оболочки с Кодексом крючка равен HSHELL_APPCOMMAND.

Таким образом, когда окно приложения находится на переднем плане, когда вы нажимаете клавишу, или имитировать вход, если целенаправленный контроль или его родитель цепь не останавливает обработку сообщения и окна верхнего уровня вызывает процедуру окна по умолчанию, затем генерируется событие оболочки. Вы должны упомянуть тех, кто вел себя в качестве медиаплееров, которые имеют registered, чтобы получать сообщения об ошибках оболочки. Этот механизм также объясняется в этом MSDN blog post.

Что OwnCloud делает, очевидно, не вызывая процедуру окна по умолчанию после получения WM_APPCOMMAND. Когда это находится на переднем плане, это приводит к вашей проблеме; другие мультимедийные приложения не могут реагировать на сообщение, поскольку не генерируется событие оболочки.

Следовательно, ваш обходной способ пытается активировать неинтерферирующее окно и генерировать ввод с клавиатуры в это окно. Это бессмысленно, окно вашего собственного приложения так же хорошо, как окно панели задач или любого другого приложения, если вы ничего не делаете с этим сообщением. Когда окно приложения находится на переднем плане и вы генерируете ввод, если проигрыватель Windows Media не отвечает, есть что-то еще, и для этого нам понадобится случай воспроизведения.

В любом случае генерация события оболочки не будет полезна для OwnCloud или подобных приложений, так как у них нет зарегистрированного окна для уведомлений об ошибках оболочки. К этим приложениям вы можете сами отправить WM_APPCOMMAND. Пример связанного сообщения в блоге MSDN имеет пример этого.Хотя это не предлагается, вы также можете транслировать сообщение, если у вас есть возможность запуска неограниченного количества мультимедийных приложений. По ряду соображений, почему это не рекомендуется, см. Этот MSDN blog post. Если вы не видите проблемы с передачей сообщения, подумайте об использовании SendMessageTimeout вместо SendMessage.