在 Node.js 应用程序中,我需要在默认音频设备更改时收到通知。该程序将在Windows 7上使用。
目前,我正在尝试通过为节点制作一个C++附加组件来做到这一点,该附加组件通过Windows Core Audio API中的IMMNotificationClient::OnDefaultDeviceChanged方法发出一个可以被Node事件发射器接收的事件。
下面是 Windows 核心音频 API 回调方法的外观示例:
HRESULT STDMETHODCALLTYPE OnDefaultDeviceChanged(EDataFlow flow, ERole role, LPCWSTR pwstrDeviceID)
{
//User written code that emits a node Event.
}
以下是我希望在上述回调函数中拥有的一些理想的 c++ 风格的伪代码:
EventEmitter.emit(v8::String::NewFromUtf8("defaultDeviceChanged"), deviceName);
我不确定如何做到这一点,所以我的问题来了:如何通过 C++ 回调函数向 Node.js 应用程序发出事件?
我对其他解决方案持开放态度,只要它们可以在 Node(IE 通过附加组件(上本机运行,并且可以在 Windows 7 上运行,这意味着对 nircmd 等应用程序的外部调用不在桌面上。
编辑
这是一些实验性代码,试图帮助描述我正在做的事情:
AudioDeviceEmitter.h:
#pragma once
#include <stdio.h>
#include <wchar.h>
#include <tchar.h>
#include <time.h>
#include "windows.h"
#include "Mmdeviceapi.h"
#include "Propidl.h"
#include "Functiondiscoverykeys_devpkey.h"
#include <vector>
#include <string>
#include <comdef.h>
#define NAPI_DISABLE_CPP_EXCEPTIONS
#include <napi.h>
#include <vector>
class AudioDeviceEmitter : public Napi::ObjectWrap<AudioDeviceEmitter>, public IMMNotificationClient {
public:
static Napi::Object Init(Napi::Env env, Napi::Object exports);
AudioDeviceEmitter(const Napi::CallbackInfo& info);
Napi::Value AudioDeviceEmitter::enrollInNotifications(const Napi::CallbackInfo& info);
Napi::Value AudioDeviceEmitter::unenrollInNotifications(const Napi::CallbackInfo& info);
//WIN API
~AudioDeviceEmitter()
{
if (_pEnumerator != NULL) {
_pEnumerator->UnregisterEndpointNotificationCallback(this);
_pEnumerator->Release();
}
}
// IUnknown methods -- AddRef, Release, and QueryInterface
ULONG STDMETHODCALLTYPE AddRef()
{
return InterlockedIncrement(&_cRef);
}
ULONG STDMETHODCALLTYPE Release()
{
ULONG ulRef = InterlockedDecrement(&_cRef);
if (0 == ulRef)
{
delete this;
}
return ulRef;
}
HRESULT STDMETHODCALLTYPE QueryInterface(
REFIID riid, VOID **ppvInterface)
{
if (IID_IUnknown == riid)
{
AddRef();
*ppvInterface = (IUnknown*)this;
}
else if (__uuidof(IMMNotificationClient) == riid)
{
AddRef();
*ppvInterface = (IMMNotificationClient*)this;
}
else
{
*ppvInterface = NULL;
return E_NOINTERFACE;
}
return S_OK;
}
// Callback methods for device-event notifications.
//------------THIS IS A FUNCTION I AM TRYING TO GET TO WORK----------
//------------It doesnt currently work, but hopefully shows----------
//------------ what I'm trying to accomplish ----------
HRESULT STDMETHODCALLTYPE OnDefaultDeviceChanged(
EDataFlow flow, ERole role,
LPCWSTR pwstrDeviceId)
{
if (flow == eRender) {
_locked = true;
std::string name = "";
IMMDevice * pDevice = NULL;
HRESULT hr = _pEnumerator->GetDevice(pwstrDeviceId, &pDevice);
if(SUCCEEDED(hr)){
name = getFriendlyNameString(pDevice);
pDevice->Release();
}
for(int i = 0; i < _stillEnrolled.size(); i++) {
if(_stillEnrolled.at(i)) {
Napi::CallbackInfo & info = _enrolledSessions.at(i);
Napi::Env env = info.Env();
Napi::Function emit = info.This().As<Napi::Object>()
.Get("emit").As<Napi::Function>();
emit.Call(info.This(), { Napi::String::New(env, "defaultDeviceChanged"),
Napi::String::New(env, name.c_str())});
}
}
_locked = false;
}
return S_OK;
}
HRESULT STDMETHODCALLTYPE OnDeviceAdded(LPCWSTR pwstrDeviceId)
{
return S_OK;
};
HRESULT STDMETHODCALLTYPE OnDeviceRemoved(LPCWSTR pwstrDeviceId)
{
return S_OK;
}
HRESULT STDMETHODCALLTYPE OnDeviceStateChanged(
LPCWSTR pwstrDeviceId,
DWORD dwNewState)
{
return S_OK;
}
HRESULT STDMETHODCALLTYPE OnPropertyValueChanged(
LPCWSTR pwstrDeviceId,
const PROPERTYKEY key)
{
return S_OK;
}
private:
std::string getFriendlyNameString(IMMDevice *pDevice) {
HRESULT hr;
IPropertyStore *pStore;
hr = pDevice->OpenPropertyStore(STGM_READ, &pStore);
PROPVARIANT variant;
PropVariantInit(&variant);
hr = pStore->GetValue(PKEY_Device_FriendlyName, &variant);
size_t strlen = wcslen((wchar_t *)variant.pwszVal);
int throwAwaylen;
char *pOutBuffer = (char *)malloc(strlen);
wcstombs_s((size_t *)&throwAwaylen, pOutBuffer, size, wideCharArray, size);
std::string toReturn = pOutBuffer;
free(pOutBuffer);
PropVariantClear(&variant);
pStore->Release();
return toReturn;
}
LONG _cRef;
IMMDeviceEnumerator *_pEnumerator;
static Napi::FunctionReference constructor;
std::vector<Napi::CallbackInfo> _enrolledSessions;
std::vector<bool> _stillEnrolled;
bool _locked;
};
音频设备发射器.cpp
#include "AudioDeviceEmitter.h"
Napi::FunctionReference AudioDeviceEmitter::constructor;
Napi::Object AudioDeviceEmitter::Init(Napi::Env env, Napi::Object exports) {
Napi::HandleScope scope(env);
Napi::Function func = DefineClass(env, "AudioDeviceEmitter", {
InstanceMethod("enrollInNotifications", &AudioDeviceEmitter::enrollInNotifications),
InstanceMethod("unenrollInNotifications", &AudioDeviceEmitter::unenrollInNotifications)
});
constructor = Napi::Persistent(func);
constructor.SuppressDestruct();
exports.Set("AudioDeviceEmitter", func);
return exports;
}
AudioDeviceEmitter::AudioDeviceEmitter(const Napi::CallbackInfo& info)
: Napi::ObjectWrap<AudioDeviceEmitter>(info), _locked(false), _cRef(1),
_pEnumerator(NULL)
{
HRESULT hr = CoInitialize(NULL);
hr = CoCreateInstance(__uuidof(MMDeviceEnumerator), NULL,
CLSCTX_ALL, __uuidof(IMMDeviceEnumerator), (void**)&_pEnumerator);
_pEnumerator->RegisterEndpointNotificationCallback(this);
}
//------------THIS IS A FUNCTION I AM TRYING TO GET TO WORK----------
//------------It doesnt currently work, but hopefully shows----------
//------------ what I'm trying to accomplish ----------
Napi::Value AudioDeviceEmitter::enrollInNotifications(const Napi::CallbackInfo& info) {
while(_locked){}
_locked = true;
int currentPos = _enrolledSessions.size();
_enrolledSessions.push_back(info);
_stillEnrolled.push_back(true);
_locked = false;
while(_stillEnrolled.at(currentPos)){}
return Napi::String::New(info.Env(), "OK");
}
//------------THIS IS A FUNCTION I AM TRYING TO GET TO WORK----------
//------------It doesnt currently work, but hopefully shows----------
//------------ what I'm trying to accomplish ----------
Napi::Value AudioDeviceEmitter::unenrollInNotifications(const Napi::CallbackInfo& info) {
while(_locked){}
for(int i = 0; i < _enrolledSessions.size(); i++) {
if (info.This() == _enrolledSessions.at(i).This()) {
_stillEnrolled.at(i) = false;
}
}
return Napi::String::New(info.Env(), "OK");
}
绑定.cpp:
#include <napi.h>
#include "AudioDeviceEmitter.h"
Napi::Object InitNAPI(Napi::Env env, Napi::Object exports) {
AudioDeviceEmitter::Init(env, exports);
return exports;
}
NODE_API_MODULE(WinDefaultAudioDevice, InitNAPI)
这段代码抛出错误并且无法编译,希望在这里更全面地解释我的问题。
实现 Nan 的 AsyncWorker 类之一。有关如何执行此操作的一些示例代码,请查看此答案。实现工作线程后,将回调函数分配给异步工作线程。