native-recorder-nodejs 1.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CMakeLists.txt +110 -0
- package/README.md +380 -0
- package/dist/bindings.d.ts +2 -0
- package/dist/bindings.js +31 -0
- package/dist/index.d.ts +104 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +110 -0
- package/dist/index.js.map +1 -0
- package/dist/recorder.d.ts +56 -0
- package/dist/recorder.d.ts.map +1 -0
- package/dist/recorder.js +343 -0
- package/dist/recorder.js.map +1 -0
- package/dist/types.d.ts +108 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +49 -0
- package/dist/types.js.map +1 -0
- package/native/AudioController.cpp +233 -0
- package/native/AudioController.h +26 -0
- package/native/AudioEngine.h +75 -0
- package/native/Factory.cpp +18 -0
- package/native/mac/AVFEngine.h +24 -0
- package/native/mac/AVFEngine.mm +274 -0
- package/native/mac/SCKAudioCapture.h +13 -0
- package/native/mac/SCKAudioCapture.mm +213 -0
- package/native/main.cpp +9 -0
- package/native/win/WASAPIEngine.cpp +449 -0
- package/native/win/WASAPIEngine.h +44 -0
- package/package.json +74 -0
- package/prebuilds/native-audio-recorder-v0.1.0-napi-v8-darwin-arm64.tar.gz +0 -0
- package/prebuilds/native-recorder-nodejs-v1.0.0-napi-v8-darwin-arm64.tar.gz +0 -0
- package/src/bindings.ts +39 -0
- package/src/index.ts +206 -0
package/dist/types.js
ADDED
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.RecordingState = exports.AudioSDKError = exports.AudioSDKErrorType = exports.AudioDeviceType = void 0;
|
|
4
|
+
/**
|
|
5
|
+
* Audio device types
|
|
6
|
+
*/
|
|
7
|
+
var AudioDeviceType;
|
|
8
|
+
(function (AudioDeviceType) {
|
|
9
|
+
AudioDeviceType[AudioDeviceType["MICROPHONE"] = 0] = "MICROPHONE";
|
|
10
|
+
AudioDeviceType[AudioDeviceType["SYSTEM_AUDIO"] = 1] = "SYSTEM_AUDIO";
|
|
11
|
+
AudioDeviceType[AudioDeviceType["APPLICATION"] = 2] = "APPLICATION";
|
|
12
|
+
})(AudioDeviceType || (exports.AudioDeviceType = AudioDeviceType = {}));
|
|
13
|
+
/**
|
|
14
|
+
* Error types
|
|
15
|
+
*/
|
|
16
|
+
var AudioSDKErrorType;
|
|
17
|
+
(function (AudioSDKErrorType) {
|
|
18
|
+
AudioSDKErrorType["PERMISSION_DENIED"] = "PERMISSION_DENIED";
|
|
19
|
+
AudioSDKErrorType["DEVICE_NOT_FOUND"] = "DEVICE_NOT_FOUND";
|
|
20
|
+
AudioSDKErrorType["DEVICE_BUSY"] = "DEVICE_BUSY";
|
|
21
|
+
AudioSDKErrorType["INITIALIZATION_FAILED"] = "INITIALIZATION_FAILED";
|
|
22
|
+
AudioSDKErrorType["RECORDING_FAILED"] = "RECORDING_FAILED";
|
|
23
|
+
AudioSDKErrorType["INVALID_FORMAT"] = "INVALID_FORMAT";
|
|
24
|
+
AudioSDKErrorType["PLATFORM_NOT_SUPPORTED"] = "PLATFORM_NOT_SUPPORTED";
|
|
25
|
+
})(AudioSDKErrorType || (exports.AudioSDKErrorType = AudioSDKErrorType = {}));
|
|
26
|
+
/**
|
|
27
|
+
* Custom error class for Audio SDK
|
|
28
|
+
*/
|
|
29
|
+
class AudioSDKError extends Error {
|
|
30
|
+
constructor(type, message, details) {
|
|
31
|
+
super(message);
|
|
32
|
+
this.type = type;
|
|
33
|
+
this.details = details;
|
|
34
|
+
this.name = "AudioSDKError";
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
exports.AudioSDKError = AudioSDKError;
|
|
38
|
+
/**
|
|
39
|
+
* Recording state
|
|
40
|
+
*/
|
|
41
|
+
var RecordingState;
|
|
42
|
+
(function (RecordingState) {
|
|
43
|
+
RecordingState["IDLE"] = "idle";
|
|
44
|
+
RecordingState["RECORDING"] = "recording";
|
|
45
|
+
RecordingState["PAUSED"] = "paused";
|
|
46
|
+
RecordingState["STOPPED"] = "stopped";
|
|
47
|
+
RecordingState["ERROR"] = "error";
|
|
48
|
+
})(RecordingState || (exports.RecordingState = RecordingState = {}));
|
|
49
|
+
//# sourceMappingURL=types.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"types.js","sourceRoot":"","sources":["../src/typescript/types.ts"],"names":[],"mappings":";;;AAOA;;GAEG;AACH,IAAY,eAIX;AAJD,WAAY,eAAe;IACzB,iEAAc,CAAA;IACd,qEAAgB,CAAA;IAChB,mEAAe,CAAA;AACjB,CAAC,EAJW,eAAe,+BAAf,eAAe,QAI1B;AAyDD;;GAEG;AACH,IAAY,iBAQX;AARD,WAAY,iBAAiB;IAC3B,4DAAuC,CAAA;IACvC,0DAAqC,CAAA;IACrC,gDAA2B,CAAA;IAC3B,oEAA+C,CAAA;IAC/C,0DAAqC,CAAA;IACrC,sDAAiC,CAAA;IACjC,sEAAiD,CAAA;AACnD,CAAC,EARW,iBAAiB,iCAAjB,iBAAiB,QAQ5B;AAED;;GAEG;AACH,MAAa,aAAc,SAAQ,KAAK;IACtC,YACS,IAAuB,EAC9B,OAAe,EACR,OAAa;QAEpB,KAAK,CAAC,OAAO,CAAC,CAAC;QAJR,SAAI,GAAJ,IAAI,CAAmB;QAEvB,YAAO,GAAP,OAAO,CAAM;QAGpB,IAAI,CAAC,IAAI,GAAG,eAAe,CAAC;IAC9B,CAAC;CACF;AATD,sCASC;AAED;;GAEG;AACH,IAAY,cAMX;AAND,WAAY,cAAc;IACxB,+BAAa,CAAA;IACb,yCAAuB,CAAA;IACvB,mCAAiB,CAAA;IACjB,qCAAmB,CAAA;IACnB,iCAAe,CAAA;AACjB,CAAC,EANW,cAAc,8BAAd,cAAc,QAMzB"}
|
|
@@ -0,0 +1,233 @@
|
|
|
1
|
+
#include "AudioController.h"
|
|
2
|
+
|
|
3
|
+
// Forward declaration of platform-specific factory
|
|
4
|
+
std::unique_ptr<AudioEngine> CreatePlatformAudioEngine();
|
|
5
|
+
|
|
6
|
+
Napi::FunctionReference AudioController::constructor;
|
|
7
|
+
|
|
8
|
+
Napi::Object AudioController::Init(Napi::Env env, Napi::Object exports) {
|
|
9
|
+
Napi::HandleScope scope(env);
|
|
10
|
+
|
|
11
|
+
Napi::Function func = DefineClass(
|
|
12
|
+
env, "AudioController",
|
|
13
|
+
{InstanceMethod("start", &AudioController::Start),
|
|
14
|
+
InstanceMethod("stop", &AudioController::Stop),
|
|
15
|
+
StaticMethod("getDevices", &AudioController::GetDevices),
|
|
16
|
+
StaticMethod("getDeviceFormat", &AudioController::GetDeviceFormat),
|
|
17
|
+
StaticMethod("checkPermission", &AudioController::CheckPermission),
|
|
18
|
+
StaticMethod("requestPermission", &AudioController::RequestPermission)});
|
|
19
|
+
|
|
20
|
+
constructor = Napi::Persistent(func);
|
|
21
|
+
constructor.SuppressDestruct();
|
|
22
|
+
|
|
23
|
+
exports.Set("AudioController", func);
|
|
24
|
+
return exports;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
AudioController::AudioController(const Napi::CallbackInfo &info)
|
|
28
|
+
: Napi::ObjectWrap<AudioController>(info) {
|
|
29
|
+
this->engine = CreatePlatformAudioEngine();
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
AudioController::~AudioController() {
|
|
33
|
+
if (this->engine) {
|
|
34
|
+
this->engine->Stop();
|
|
35
|
+
}
|
|
36
|
+
if (this->tsfn) {
|
|
37
|
+
this->tsfn->Release();
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
Napi::Value AudioController::Start(const Napi::CallbackInfo &info) {
|
|
42
|
+
Napi::Env env = info.Env();
|
|
43
|
+
|
|
44
|
+
if (info.Length() < 2 || !info[0].IsObject() || !info[1].IsFunction()) {
|
|
45
|
+
Napi::TypeError::New(env, "Expected config object and callback function")
|
|
46
|
+
.ThrowAsJavaScriptException();
|
|
47
|
+
return env.Null();
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
Napi::Object config = info[0].As<Napi::Object>();
|
|
51
|
+
|
|
52
|
+
// Parse deviceType (required)
|
|
53
|
+
std::string deviceType = AudioEngine::DEVICE_TYPE_INPUT; // default to input
|
|
54
|
+
if (config.Has("deviceType")) {
|
|
55
|
+
Napi::Value typeVal = config.Get("deviceType");
|
|
56
|
+
if (typeVal.IsString()) {
|
|
57
|
+
deviceType = typeVal.As<Napi::String>().Utf8Value();
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
// Validate deviceType
|
|
62
|
+
if (deviceType != AudioEngine::DEVICE_TYPE_INPUT &&
|
|
63
|
+
deviceType != AudioEngine::DEVICE_TYPE_OUTPUT) {
|
|
64
|
+
Napi::TypeError::New(env, "deviceType must be 'input' or 'output'")
|
|
65
|
+
.ThrowAsJavaScriptException();
|
|
66
|
+
return env.Null();
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
// Parse deviceId (required)
|
|
70
|
+
std::string deviceId = "";
|
|
71
|
+
if (config.Has("deviceId")) {
|
|
72
|
+
Napi::Value idVal = config.Get("deviceId");
|
|
73
|
+
if (idVal.IsString()) {
|
|
74
|
+
deviceId = idVal.As<Napi::String>().Utf8Value();
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
if (deviceId.empty()) {
|
|
79
|
+
Napi::TypeError::New(env, "deviceId is required")
|
|
80
|
+
.ThrowAsJavaScriptException();
|
|
81
|
+
return env.Null();
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
Napi::Function callback = info[1].As<Napi::Function>();
|
|
85
|
+
|
|
86
|
+
// Create a ThreadSafeFunction to call back into JS from the audio thread
|
|
87
|
+
this->tsfn = std::make_shared<Napi::ThreadSafeFunction>(
|
|
88
|
+
Napi::ThreadSafeFunction::New(env, callback, "AudioDataCallback", 0, 1));
|
|
89
|
+
|
|
90
|
+
auto dataCallback = [tsfn = this->tsfn](const uint8_t *data, size_t size) {
|
|
91
|
+
// Copy data to a vector to pass to the JS thread safely
|
|
92
|
+
auto dataVec = new std::vector<uint8_t>(data, data + size);
|
|
93
|
+
|
|
94
|
+
napi_status status =
|
|
95
|
+
tsfn->BlockingCall(dataVec, [](Napi::Env env, Napi::Function jsCallback,
|
|
96
|
+
std::vector<uint8_t> *vec) {
|
|
97
|
+
// This runs on the JS main thread
|
|
98
|
+
Napi::Buffer<uint8_t> buffer =
|
|
99
|
+
Napi::Buffer<uint8_t>::Copy(env, vec->data(), vec->size());
|
|
100
|
+
jsCallback.Call({env.Null(), buffer});
|
|
101
|
+
delete vec;
|
|
102
|
+
});
|
|
103
|
+
|
|
104
|
+
if (status != napi_ok) {
|
|
105
|
+
// Handle error or shutdown
|
|
106
|
+
delete dataVec;
|
|
107
|
+
}
|
|
108
|
+
};
|
|
109
|
+
|
|
110
|
+
auto errorCallback = [tsfn = this->tsfn](const std::string &errorMsg) {
|
|
111
|
+
auto errorStr = new std::string(errorMsg);
|
|
112
|
+
napi_status status = tsfn->BlockingCall(
|
|
113
|
+
errorStr,
|
|
114
|
+
[](Napi::Env env, Napi::Function jsCallback, std::string *str) {
|
|
115
|
+
// Pass error to JS callback as first argument
|
|
116
|
+
jsCallback.Call({Napi::Error::New(env, *str).Value(), env.Null()});
|
|
117
|
+
delete str;
|
|
118
|
+
});
|
|
119
|
+
if (status != napi_ok) {
|
|
120
|
+
delete errorStr;
|
|
121
|
+
}
|
|
122
|
+
};
|
|
123
|
+
|
|
124
|
+
try {
|
|
125
|
+
this->engine->Start(deviceType, deviceId, dataCallback, errorCallback);
|
|
126
|
+
} catch (const std::exception &e) {
|
|
127
|
+
Napi::Error::New(env, e.what()).ThrowAsJavaScriptException();
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
return env.Null();
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
Napi::Value AudioController::Stop(const Napi::CallbackInfo &info) {
|
|
134
|
+
if (this->engine) {
|
|
135
|
+
this->engine->Stop();
|
|
136
|
+
}
|
|
137
|
+
if (this->tsfn) {
|
|
138
|
+
this->tsfn->Release();
|
|
139
|
+
this->tsfn = nullptr;
|
|
140
|
+
}
|
|
141
|
+
return info.Env().Null();
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
Napi::Value AudioController::GetDevices(const Napi::CallbackInfo &info) {
|
|
145
|
+
Napi::Env env = info.Env();
|
|
146
|
+
|
|
147
|
+
auto tempEngine = CreatePlatformAudioEngine();
|
|
148
|
+
std::vector<AudioDevice> devices = tempEngine->GetDevices();
|
|
149
|
+
|
|
150
|
+
Napi::Array result = Napi::Array::New(env, devices.size());
|
|
151
|
+
for (size_t i = 0; i < devices.size(); i++) {
|
|
152
|
+
Napi::Object deviceObj = Napi::Object::New(env);
|
|
153
|
+
deviceObj.Set("id", devices[i].id);
|
|
154
|
+
deviceObj.Set("name", devices[i].name);
|
|
155
|
+
deviceObj.Set("type", devices[i].type);
|
|
156
|
+
deviceObj.Set("isDefault", devices[i].isDefault);
|
|
157
|
+
result[i] = deviceObj;
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
return result;
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
Napi::Value AudioController::GetDeviceFormat(const Napi::CallbackInfo &info) {
|
|
164
|
+
Napi::Env env = info.Env();
|
|
165
|
+
|
|
166
|
+
if (info.Length() < 1 || !info[0].IsString()) {
|
|
167
|
+
Napi::TypeError::New(env, "Expected deviceId string")
|
|
168
|
+
.ThrowAsJavaScriptException();
|
|
169
|
+
return env.Null();
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
std::string deviceId = info[0].As<Napi::String>().Utf8Value();
|
|
173
|
+
|
|
174
|
+
auto tempEngine = CreatePlatformAudioEngine();
|
|
175
|
+
AudioFormat format = tempEngine->GetDeviceFormat(deviceId);
|
|
176
|
+
|
|
177
|
+
if (format.sampleRate == 0) {
|
|
178
|
+
Napi::Error::New(env, "Failed to get device format")
|
|
179
|
+
.ThrowAsJavaScriptException();
|
|
180
|
+
return env.Null();
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
Napi::Object result = Napi::Object::New(env);
|
|
184
|
+
result.Set("sampleRate", format.sampleRate);
|
|
185
|
+
result.Set("channels", format.channels);
|
|
186
|
+
result.Set("bitDepth", format.bitDepth);
|
|
187
|
+
result.Set("rawBitDepth", format.rawBitDepth);
|
|
188
|
+
|
|
189
|
+
return result;
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
Napi::Value AudioController::CheckPermission(const Napi::CallbackInfo &info) {
|
|
193
|
+
Napi::Env env = info.Env();
|
|
194
|
+
|
|
195
|
+
auto tempEngine = CreatePlatformAudioEngine();
|
|
196
|
+
PermissionStatus status = tempEngine->CheckPermission();
|
|
197
|
+
|
|
198
|
+
Napi::Object result = Napi::Object::New(env);
|
|
199
|
+
result.Set("mic", status.mic);
|
|
200
|
+
result.Set("system", status.system);
|
|
201
|
+
|
|
202
|
+
return result;
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
Napi::Value AudioController::RequestPermission(const Napi::CallbackInfo &info) {
|
|
206
|
+
Napi::Env env = info.Env();
|
|
207
|
+
|
|
208
|
+
if (info.Length() < 1 || !info[0].IsString()) {
|
|
209
|
+
Napi::TypeError::New(env,
|
|
210
|
+
"Expected permission type string ('mic' or 'system')")
|
|
211
|
+
.ThrowAsJavaScriptException();
|
|
212
|
+
return env.Null();
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
std::string typeStr = info[0].As<Napi::String>().Utf8Value();
|
|
216
|
+
PermissionType type;
|
|
217
|
+
|
|
218
|
+
if (typeStr == AudioEngine::PERMISSION_MIC) {
|
|
219
|
+
type = PermissionType::Mic;
|
|
220
|
+
} else if (typeStr == AudioEngine::PERMISSION_SYSTEM) {
|
|
221
|
+
type = PermissionType::System;
|
|
222
|
+
} else {
|
|
223
|
+
Napi::TypeError::New(env,
|
|
224
|
+
"Invalid permission type. Must be 'mic' or 'system'")
|
|
225
|
+
.ThrowAsJavaScriptException();
|
|
226
|
+
return env.Null();
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
auto tempEngine = CreatePlatformAudioEngine();
|
|
230
|
+
bool granted = tempEngine->RequestPermission(type);
|
|
231
|
+
|
|
232
|
+
return Napi::Boolean::New(env, granted);
|
|
233
|
+
}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
#pragma once
|
|
2
|
+
|
|
3
|
+
#include "AudioEngine.h"
|
|
4
|
+
#include <memory>
|
|
5
|
+
#include <napi.h>
|
|
6
|
+
#include <thread>
|
|
7
|
+
|
|
8
|
+
class AudioController : public Napi::ObjectWrap<AudioController> {
|
|
9
|
+
public:
|
|
10
|
+
static Napi::Object Init(Napi::Env env, Napi::Object exports);
|
|
11
|
+
AudioController(const Napi::CallbackInfo &info);
|
|
12
|
+
~AudioController();
|
|
13
|
+
|
|
14
|
+
private:
|
|
15
|
+
static Napi::FunctionReference constructor;
|
|
16
|
+
|
|
17
|
+
Napi::Value Start(const Napi::CallbackInfo &info);
|
|
18
|
+
Napi::Value Stop(const Napi::CallbackInfo &info);
|
|
19
|
+
static Napi::Value GetDevices(const Napi::CallbackInfo &info);
|
|
20
|
+
static Napi::Value GetDeviceFormat(const Napi::CallbackInfo &info);
|
|
21
|
+
static Napi::Value CheckPermission(const Napi::CallbackInfo &info);
|
|
22
|
+
static Napi::Value RequestPermission(const Napi::CallbackInfo &info);
|
|
23
|
+
|
|
24
|
+
std::unique_ptr<AudioEngine> engine;
|
|
25
|
+
std::shared_ptr<Napi::ThreadSafeFunction> tsfn;
|
|
26
|
+
};
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
#pragma once
|
|
2
|
+
|
|
3
|
+
#include <cstdint>
|
|
4
|
+
#include <functional>
|
|
5
|
+
#include <string>
|
|
6
|
+
#include <vector>
|
|
7
|
+
|
|
8
|
+
struct AudioDevice {
|
|
9
|
+
std::string id;
|
|
10
|
+
std::string name;
|
|
11
|
+
std::string type; // "input" or "output"
|
|
12
|
+
bool isDefault;
|
|
13
|
+
};
|
|
14
|
+
|
|
15
|
+
struct AudioFormat {
|
|
16
|
+
int sampleRate;
|
|
17
|
+
int channels;
|
|
18
|
+
int bitDepth; // Output bit depth (always 16 for now)
|
|
19
|
+
int rawBitDepth; // Native device bit depth
|
|
20
|
+
};
|
|
21
|
+
|
|
22
|
+
// Permission status for audio recording
|
|
23
|
+
struct PermissionStatus {
|
|
24
|
+
bool mic; // Microphone permission granted
|
|
25
|
+
bool system; // System audio permission granted
|
|
26
|
+
};
|
|
27
|
+
|
|
28
|
+
// Permission type for requesting
|
|
29
|
+
enum class PermissionType { Mic, System };
|
|
30
|
+
|
|
31
|
+
class AudioEngine {
|
|
32
|
+
public:
|
|
33
|
+
virtual ~AudioEngine() = default;
|
|
34
|
+
|
|
35
|
+
// Device type constants
|
|
36
|
+
static constexpr const char *DEVICE_TYPE_INPUT = "input";
|
|
37
|
+
static constexpr const char *DEVICE_TYPE_OUTPUT = "output";
|
|
38
|
+
|
|
39
|
+
// Special device ID for system-wide audio capture (macOS)
|
|
40
|
+
static constexpr const char *SYSTEM_AUDIO_DEVICE_ID = "system";
|
|
41
|
+
|
|
42
|
+
// Permission type constants for JS
|
|
43
|
+
static constexpr const char *PERMISSION_MIC = "mic";
|
|
44
|
+
static constexpr const char *PERMISSION_SYSTEM = "system";
|
|
45
|
+
|
|
46
|
+
// Callback for receiving raw PCM data (16-bit, Native Sample Rate,
|
|
47
|
+
// Stereo/Mono)
|
|
48
|
+
using DataCallback = std::function<void(const uint8_t *data, size_t size)>;
|
|
49
|
+
|
|
50
|
+
// Callback for receiving error messages
|
|
51
|
+
using ErrorCallback = std::function<void(const std::string &error)>;
|
|
52
|
+
|
|
53
|
+
// Start recording with explicit device type and ID
|
|
54
|
+
// deviceType: "input" or "output"
|
|
55
|
+
// deviceId: device identifier from GetDevices() (never empty)
|
|
56
|
+
virtual void Start(const std::string &deviceType, const std::string &deviceId,
|
|
57
|
+
DataCallback dataCb, ErrorCallback errorCb) = 0;
|
|
58
|
+
virtual void Stop() = 0;
|
|
59
|
+
|
|
60
|
+
// Get all available devices (both input and output)
|
|
61
|
+
// All returned devices have valid id and type fields
|
|
62
|
+
virtual std::vector<AudioDevice> GetDevices() = 0;
|
|
63
|
+
|
|
64
|
+
// Get format for specific device
|
|
65
|
+
virtual AudioFormat GetDeviceFormat(const std::string &deviceId) = 0;
|
|
66
|
+
|
|
67
|
+
// Check permission status for mic and system audio
|
|
68
|
+
// Returns current permission status without prompting user
|
|
69
|
+
virtual PermissionStatus CheckPermission() = 0;
|
|
70
|
+
|
|
71
|
+
// Request permission for specified type
|
|
72
|
+
// Returns true if permission was granted, false otherwise
|
|
73
|
+
// On Windows, always returns true (no permission needed)
|
|
74
|
+
virtual bool RequestPermission(PermissionType type) = 0;
|
|
75
|
+
};
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
#include "AudioEngine.h"
|
|
2
|
+
#include <memory>
|
|
3
|
+
|
|
4
|
+
#ifdef _WIN32
|
|
5
|
+
#include "win/WASAPIEngine.h"
|
|
6
|
+
#elif defined(__APPLE__)
|
|
7
|
+
#include "mac/AVFEngine.h"
|
|
8
|
+
#endif
|
|
9
|
+
|
|
10
|
+
std::unique_ptr<AudioEngine> CreatePlatformAudioEngine() {
|
|
11
|
+
#ifdef _WIN32
|
|
12
|
+
return std::make_unique<WASAPIEngine>();
|
|
13
|
+
#elif defined(__APPLE__)
|
|
14
|
+
return std::make_unique<AVFEngine>();
|
|
15
|
+
#else
|
|
16
|
+
return nullptr;
|
|
17
|
+
#endif
|
|
18
|
+
}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
#pragma once
|
|
2
|
+
|
|
3
|
+
#include "../AudioEngine.h"
|
|
4
|
+
#include <memory>
|
|
5
|
+
|
|
6
|
+
class AVFEngine : public AudioEngine {
|
|
7
|
+
public:
|
|
8
|
+
AVFEngine();
|
|
9
|
+
~AVFEngine();
|
|
10
|
+
|
|
11
|
+
void Start(const std::string &deviceType, const std::string &deviceId,
|
|
12
|
+
DataCallback dataCb, ErrorCallback errorCb) override;
|
|
13
|
+
void Stop() override;
|
|
14
|
+
std::vector<AudioDevice> GetDevices() override;
|
|
15
|
+
AudioFormat GetDeviceFormat(const std::string &deviceId) override;
|
|
16
|
+
|
|
17
|
+
// Permission handling
|
|
18
|
+
PermissionStatus CheckPermission() override;
|
|
19
|
+
bool RequestPermission(PermissionType type) override;
|
|
20
|
+
|
|
21
|
+
private:
|
|
22
|
+
struct Impl;
|
|
23
|
+
std::unique_ptr<Impl> impl;
|
|
24
|
+
};
|