mirror of
https://github.com/Boof2015/prism.git
synced 2026-08-18 19:44:16 +02:00
prism-tui PoC
This commit is contained in:
+68
-150
@@ -1,4 +1,4 @@
|
||||
#include "macos_capture.h"
|
||||
#include "system_audio_capture.h"
|
||||
|
||||
#if defined(__APPLE__)
|
||||
|
||||
@@ -319,140 +319,104 @@ std::string formatStatusMessage(const char* operation, OSStatus status) {
|
||||
return std::string(operation) + " failed (" + std::to_string(static_cast<int>(status)) + ")";
|
||||
}
|
||||
|
||||
class MacOSNativeCaptureEngine {
|
||||
class MacOSNativeCaptureEngine final : public Prism::Capture::SystemAudioCapture {
|
||||
public:
|
||||
Napi::Object GetSupport(Napi::Env env) {
|
||||
Napi::Object support = Napi::Object::New(env);
|
||||
|
||||
if (@available(macOS 14.2, *)) {
|
||||
support.Set("available", Napi::Boolean::New(env, true));
|
||||
support.Set("reason", env.Null());
|
||||
return support;
|
||||
}
|
||||
|
||||
support.Set("available", Napi::Boolean::New(env, false));
|
||||
support.Set(
|
||||
"reason",
|
||||
Napi::String::New(env, "Native output-device capture requires macOS 14.2 or newer."));
|
||||
return support;
|
||||
~MacOSNativeCaptureEngine() override {
|
||||
stop();
|
||||
}
|
||||
|
||||
Napi::Array ListOutputDevices(Napi::Env env) {
|
||||
Napi::Array result = Napi::Array::New(env);
|
||||
Prism::Capture::Support getSupport() const override {
|
||||
if (@available(macOS 14.2, *)) {
|
||||
return {true, {}};
|
||||
}
|
||||
return {false, "Native output-device capture requires macOS 14.2 or newer."};
|
||||
}
|
||||
|
||||
std::vector<Prism::Capture::OutputDevice> listOutputDevices() override {
|
||||
std::vector<Prism::Capture::OutputDevice> result;
|
||||
if (!isSupported()) {
|
||||
return result;
|
||||
}
|
||||
|
||||
const auto devices = enumerateOutputDevices();
|
||||
for (size_t index = 0; index < devices.size(); ++index) {
|
||||
const auto& device = devices[index];
|
||||
Napi::Object entry = Napi::Object::New(env);
|
||||
entry.Set("id", Napi::String::New(env, device.uid));
|
||||
entry.Set("label", Napi::String::New(env, device.label));
|
||||
entry.Set("kind", Napi::String::New(env, "system"));
|
||||
entry.Set("isDefault", Napi::Boolean::New(env, device.isDefault));
|
||||
entry.Set("sampleRate", Napi::Number::New(env, device.sampleRate));
|
||||
entry.Set(
|
||||
"channelCount",
|
||||
Napi::Number::New(env, static_cast<double>(device.channelCount)));
|
||||
result.Set(static_cast<uint32_t>(index), entry);
|
||||
result.reserve(devices.size());
|
||||
for (const auto& device : devices) {
|
||||
result.push_back({
|
||||
device.uid,
|
||||
device.label,
|
||||
device.sampleRate,
|
||||
static_cast<uint32_t>(device.channelCount),
|
||||
device.isDefault,
|
||||
});
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
Napi::Object Start(Napi::Env env, const std::string& requestedDeviceUid) {
|
||||
Napi::Object result = Napi::Object::New(env);
|
||||
|
||||
const Napi::Object support = GetSupport(env);
|
||||
if (!support.Get("available").As<Napi::Boolean>().Value()) {
|
||||
Napi::Error::New(
|
||||
env, support.Get("reason").As<Napi::String>().Utf8Value())
|
||||
.ThrowAsJavaScriptException();
|
||||
return result;
|
||||
bool start(const std::string& requestedDeviceId,
|
||||
Prism::Capture::StartResult* result,
|
||||
std::string* errorMessage) override {
|
||||
const auto support = getSupport();
|
||||
if (!support.available) {
|
||||
if (errorMessage != nullptr) {
|
||||
*errorMessage = support.reason;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
std::string errorMessage;
|
||||
if (!startInternal(requestedDeviceUid, &errorMessage)) {
|
||||
Napi::Error::New(env, errorMessage).ThrowAsJavaScriptException();
|
||||
return result;
|
||||
if (!startInternal(requestedDeviceId, errorMessage)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
std::lock_guard<std::mutex> lock(stateMutex_);
|
||||
result.Set("sampleRate", Napi::Number::New(env, sampleRate_));
|
||||
result.Set("channelCount", Napi::Number::New(env, static_cast<double>(channelCount_)));
|
||||
result.Set("deviceId", Napi::String::New(env, activeDeviceUid_));
|
||||
result.Set("deviceLabel", Napi::String::New(env, activeDeviceLabel_));
|
||||
return result;
|
||||
if (result != nullptr) {
|
||||
std::lock_guard<std::mutex> lock(stateMutex_);
|
||||
result->sampleRate = sampleRate_;
|
||||
result->channelCount = static_cast<uint32_t>(channelCount_);
|
||||
result->deviceId = activeDeviceUid_;
|
||||
result->deviceLabel = activeDeviceLabel_;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
void Stop() {
|
||||
void stop() override {
|
||||
std::lock_guard<std::mutex> lock(stateMutex_);
|
||||
stopLocked();
|
||||
}
|
||||
|
||||
Napi::Object Drain(Napi::Env env, size_t maxChunks) {
|
||||
Prism::Capture::DrainResult drain(size_t maxChunks) override {
|
||||
const size_t drainLimit =
|
||||
maxChunks == 0 ? kDefaultDrainChunkLimit : std::min(maxChunks, kMaxQueuedChunks);
|
||||
|
||||
std::deque<CapturedChunk> drained;
|
||||
uint64_t overwriteCount = 0;
|
||||
|
||||
Prism::Capture::DrainResult result;
|
||||
{
|
||||
std::lock_guard<std::mutex> queueLock(chunkMutex_);
|
||||
overwriteCount = overwriteCount_;
|
||||
result.overwriteCount = overwriteCount_;
|
||||
const size_t count = std::min(drainLimit, chunkQueue_.size());
|
||||
for (size_t index = 0; index < count; ++index) {
|
||||
drained.push_back(std::move(chunkQueue_.front()));
|
||||
chunkQueue_.pop_front();
|
||||
}
|
||||
result.queueDepth = chunkQueue_.size();
|
||||
}
|
||||
|
||||
Napi::Array chunks = Napi::Array::New(env, drained.size());
|
||||
for (size_t index = 0; index < drained.size(); ++index) {
|
||||
CapturedChunk& chunk = drained[index];
|
||||
Napi::Object entry = Napi::Object::New(env);
|
||||
Napi::Float32Array left =
|
||||
Napi::Float32Array::New(env, chunk.left.size());
|
||||
Napi::Float32Array right =
|
||||
Napi::Float32Array::New(env, chunk.right.size());
|
||||
if (!chunk.left.empty()) {
|
||||
std::memcpy(
|
||||
left.Data(), chunk.left.data(), chunk.left.size() * sizeof(float));
|
||||
}
|
||||
if (!chunk.right.empty()) {
|
||||
std::memcpy(
|
||||
right.Data(), chunk.right.data(), chunk.right.size() * sizeof(float));
|
||||
}
|
||||
entry.Set("left", left);
|
||||
entry.Set("right", right);
|
||||
entry.Set(
|
||||
"channelCount",
|
||||
Napi::Number::New(env, static_cast<double>(chunk.channelCount)));
|
||||
entry.Set(
|
||||
"capturedAtMilliseconds",
|
||||
Napi::Number::New(env, chunk.capturedAtMilliseconds));
|
||||
entry.Set(
|
||||
"sequence",
|
||||
Napi::Number::New(env, static_cast<double>(chunk.sequence)));
|
||||
chunks.Set(static_cast<uint32_t>(index), entry);
|
||||
result.chunks.reserve(drained.size());
|
||||
while (!drained.empty()) {
|
||||
auto chunk = std::move(drained.front());
|
||||
drained.pop_front();
|
||||
result.chunks.push_back({
|
||||
std::move(chunk.left),
|
||||
std::move(chunk.right),
|
||||
static_cast<uint32_t>(chunk.channelCount),
|
||||
chunk.capturedAtMilliseconds,
|
||||
chunk.sequence,
|
||||
});
|
||||
}
|
||||
|
||||
Napi::Object result = Napi::Object::New(env);
|
||||
result.Set("chunks", chunks);
|
||||
result.Set(
|
||||
"overwriteCount",
|
||||
Napi::Number::New(env, static_cast<double>(overwriteCount)));
|
||||
result.Set(
|
||||
"queueDepth",
|
||||
Napi::Number::New(env, static_cast<double>(chunkQueue_.size())));
|
||||
return result;
|
||||
}
|
||||
|
||||
double NowMilliseconds() const {
|
||||
double nowMilliseconds() const override {
|
||||
return monotonicMilliseconds();
|
||||
}
|
||||
|
||||
const char* backendName() const override {
|
||||
return "CoreAudio";
|
||||
}
|
||||
|
||||
private:
|
||||
static OSStatus StaticIOProc(AudioObjectID inDevice,
|
||||
const AudioTimeStamp* inNow,
|
||||
@@ -780,60 +744,14 @@ private:
|
||||
UInt32 channelCount_ = 2;
|
||||
};
|
||||
|
||||
MacOSNativeCaptureEngine& engine() {
|
||||
static MacOSNativeCaptureEngine instance;
|
||||
return instance;
|
||||
}
|
||||
|
||||
Napi::Value MacOSGetSupport(const Napi::CallbackInfo& info) {
|
||||
return engine().GetSupport(info.Env());
|
||||
}
|
||||
|
||||
Napi::Value MacOSListOutputDevices(const Napi::CallbackInfo& info) {
|
||||
return engine().ListOutputDevices(info.Env());
|
||||
}
|
||||
|
||||
Napi::Value MacOSStart(const Napi::CallbackInfo& info) {
|
||||
std::string requestedDeviceUid;
|
||||
if (info.Length() >= 1 && info[0].IsString()) {
|
||||
requestedDeviceUid = info[0].As<Napi::String>().Utf8Value();
|
||||
}
|
||||
return engine().Start(info.Env(), requestedDeviceUid);
|
||||
}
|
||||
|
||||
Napi::Value MacOSStop(const Napi::CallbackInfo& info) {
|
||||
engine().Stop();
|
||||
return info.Env().Undefined();
|
||||
}
|
||||
|
||||
Napi::Value MacOSDrain(const Napi::CallbackInfo& info) {
|
||||
size_t maxChunks = kDefaultDrainChunkLimit;
|
||||
if (info.Length() >= 1 && info[0].IsNumber()) {
|
||||
const int64_t requested = info[0].As<Napi::Number>().Int64Value();
|
||||
if (requested > 0) {
|
||||
maxChunks = static_cast<size_t>(requested);
|
||||
}
|
||||
}
|
||||
return engine().Drain(info.Env(), maxChunks);
|
||||
}
|
||||
|
||||
Napi::Value MacOSNowMilliseconds(const Napi::CallbackInfo& info) {
|
||||
return Napi::Number::New(info.Env(), engine().NowMilliseconds());
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
void RegisterMacOSCapture(Napi::Env env, Napi::Object exports) {
|
||||
Napi::Object captureExports = Napi::Object::New(env);
|
||||
captureExports.Set("getSupport", Napi::Function::New(env, MacOSGetSupport));
|
||||
captureExports.Set(
|
||||
"listOutputDevices", Napi::Function::New(env, MacOSListOutputDevices));
|
||||
captureExports.Set("start", Napi::Function::New(env, MacOSStart));
|
||||
captureExports.Set("stop", Napi::Function::New(env, MacOSStop));
|
||||
captureExports.Set("drain", Napi::Function::New(env, MacOSDrain));
|
||||
captureExports.Set(
|
||||
"nowMilliseconds", Napi::Function::New(env, MacOSNowMilliseconds));
|
||||
exports.Set("macosCapture", captureExports);
|
||||
namespace Prism::Capture {
|
||||
|
||||
std::unique_ptr<SystemAudioCapture> createSystemAudioCapture() {
|
||||
return std::make_unique<MacOSNativeCaptureEngine>();
|
||||
}
|
||||
|
||||
} // namespace Prism::Capture
|
||||
|
||||
#endif // defined(__APPLE__)
|
||||
|
||||
Reference in New Issue
Block a user