268 lines
8.1 KiB
C++
268 lines
8.1 KiB
C++
#include <utility>
|
|
#include <NanStrings.h>
|
|
|
|
#include "AudioRecorder.h"
|
|
#include "AudioConsumer.h"
|
|
#include "../AudioInput.h"
|
|
#include "../../logger.h"
|
|
|
|
using namespace std;
|
|
using namespace tc::audio;
|
|
using namespace tc::audio::recorder;
|
|
|
|
NAN_MODULE_INIT(recorder::init_js) {
|
|
Nan::Set(target, Nan::New<v8::String>("create_recorder").ToLocalChecked(), Nan::GetFunction(Nan::New<v8::FunctionTemplate>(recorder::create_recorder)).ToLocalChecked());
|
|
}
|
|
|
|
NAN_METHOD(recorder::create_recorder) {
|
|
if(!audio::initialized()) {
|
|
Nan::ThrowError(tr("audio hasn't been initialized yet"));
|
|
return;
|
|
}
|
|
auto input = make_shared<AudioInput>(2, 48000);
|
|
auto wrapper = new AudioRecorderWrapper(input);
|
|
auto js_object = Nan::NewInstance(Nan::New(AudioRecorderWrapper::constructor())).ToLocalChecked();
|
|
wrapper->do_wrap(js_object);
|
|
info.GetReturnValue().Set(js_object);
|
|
}
|
|
|
|
|
|
NAN_MODULE_INIT(AudioRecorderWrapper::Init) {
|
|
auto klass = Nan::New<v8::FunctionTemplate>(AudioRecorderWrapper::NewInstance);
|
|
klass->SetClassName(Nan::New("AudioRecorder").ToLocalChecked());
|
|
klass->InstanceTemplate()->SetInternalFieldCount(1);
|
|
|
|
Nan::SetPrototypeMethod(klass, "get_device", AudioRecorderWrapper::_get_device);
|
|
Nan::SetPrototypeMethod(klass, "set_device", AudioRecorderWrapper::_set_device);
|
|
|
|
Nan::SetPrototypeMethod(klass, "start", AudioRecorderWrapper::_start);
|
|
Nan::SetPrototypeMethod(klass, "started", AudioRecorderWrapper::_started);
|
|
Nan::SetPrototypeMethod(klass, "stop", AudioRecorderWrapper::_stop);
|
|
|
|
Nan::SetPrototypeMethod(klass, "get_volume", AudioRecorderWrapper::_get_volume);
|
|
Nan::SetPrototypeMethod(klass, "set_volume", AudioRecorderWrapper::_set_volume);
|
|
|
|
Nan::SetPrototypeMethod(klass, "get_consumers", AudioRecorderWrapper::_get_consumers);
|
|
Nan::SetPrototypeMethod(klass, "create_consumer", AudioRecorderWrapper::_create_consumer);
|
|
Nan::SetPrototypeMethod(klass, "delete_consumer", AudioRecorderWrapper::_delete_consumer);
|
|
|
|
constructor().Reset(Nan::GetFunction(klass).ToLocalChecked());
|
|
}
|
|
|
|
NAN_METHOD(AudioRecorderWrapper::NewInstance) {
|
|
if(!info.IsConstructCall())
|
|
Nan::ThrowError("invalid invoke!");
|
|
}
|
|
|
|
|
|
AudioRecorderWrapper::AudioRecorderWrapper(std::shared_ptr<tc::audio::AudioInput> handle) : _input(std::move(handle)) {
|
|
log_allocate("AudioRecorderWrapper", this);
|
|
}
|
|
AudioRecorderWrapper::~AudioRecorderWrapper() {
|
|
if(this->_input) {
|
|
this->_input->stop();
|
|
this->_input->close_device();
|
|
this->_input = nullptr;
|
|
}
|
|
{
|
|
lock_guard lock(this->_consumer_lock);
|
|
this->_consumers.clear();
|
|
}
|
|
log_free("AudioRecorderWrapper", this);
|
|
}
|
|
|
|
std::shared_ptr<AudioConsumerWrapper> AudioRecorderWrapper::create_consumer() {
|
|
auto result = shared_ptr<AudioConsumerWrapper>(new AudioConsumerWrapper(this, this->_input->create_consumer(960)), [](AudioConsumerWrapper* ptr) {
|
|
assert(v8::Isolate::GetCurrent());
|
|
ptr->Unref();
|
|
});
|
|
|
|
/* wrap into object */
|
|
{
|
|
auto js_object = Nan::NewInstance(Nan::New(AudioConsumerWrapper::constructor()), 0, nullptr).ToLocalChecked();
|
|
result->do_wrap(js_object);
|
|
result->Ref();
|
|
}
|
|
|
|
{
|
|
lock_guard lock(this->_consumer_lock);
|
|
this->_consumers.push_back(result);
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
void AudioRecorderWrapper::delete_consumer(const AudioConsumerWrapper* consumer) {
|
|
shared_ptr<AudioConsumerWrapper> handle; /* need to keep the handle 'till everything has been finished */
|
|
{
|
|
lock_guard lock(this->_consumer_lock);
|
|
for(auto& c : this->_consumers) {
|
|
if(&*c == consumer) {
|
|
handle = c;
|
|
break;
|
|
}
|
|
}
|
|
if(!handle)
|
|
return;
|
|
|
|
{
|
|
auto it = find(this->_consumers.begin(), this->_consumers.end(), handle);
|
|
if(it != this->_consumers.end())
|
|
this->_consumers.erase(it);
|
|
}
|
|
}
|
|
|
|
{
|
|
lock_guard lock(handle->execute_lock); /* if we delete the consumer while executing strange stuff could happen */
|
|
handle->unbind();
|
|
this->_input->delete_consumer(handle->_handle);
|
|
}
|
|
}
|
|
|
|
void AudioRecorderWrapper::do_wrap(const v8::Local<v8::Object> &obj) {
|
|
this->Wrap(obj);
|
|
}
|
|
|
|
NAN_METHOD(AudioRecorderWrapper::_get_device) {
|
|
auto handle = ObjectWrap::Unwrap<AudioRecorderWrapper>(info.Holder());
|
|
auto input = handle->_input;
|
|
|
|
auto device = input->current_device();
|
|
if(device)
|
|
info.GetReturnValue().Set(Nan::LocalString(device->id()));
|
|
else
|
|
info.GetReturnValue().Set(Nan::Undefined());
|
|
}
|
|
|
|
NAN_METHOD(AudioRecorderWrapper::_set_device) {
|
|
auto handle = ObjectWrap::Unwrap<AudioRecorderWrapper>(info.Holder());
|
|
auto input = handle->_input;
|
|
|
|
const auto is_null_device = info[0]->IsNullOrUndefined();
|
|
if(info.Length() != 2 || !(is_null_device || info[0]->IsString()) || !info[1]->IsFunction()) {
|
|
Nan::ThrowError("invalid arguments");
|
|
return;
|
|
}
|
|
|
|
if(!audio::initialized()) {
|
|
Nan::ThrowError("audio hasn't been initialized yet");
|
|
return;
|
|
}
|
|
|
|
auto device = is_null_device ? nullptr : audio::find_device_by_id(*Nan::Utf8String(info[0]), true);
|
|
if(!device && !is_null_device) {
|
|
Nan::ThrowError("invalid device id");
|
|
return;
|
|
}
|
|
|
|
unique_ptr<Nan::Persistent<v8::Function>> _callback = make_unique<Nan::Persistent<v8::Function>>(info[1].As<v8::Function>());
|
|
unique_ptr<Nan::Persistent<v8::Object>> _recorder = make_unique<Nan::Persistent<v8::Object>>(info.Holder());
|
|
|
|
auto _async_callback = Nan::async_callback([call = std::move(_callback), recorder = move(_recorder)] {
|
|
Nan::HandleScope scope;
|
|
auto callback_function = call->Get(Nan::GetCurrentContext()->GetIsolate());
|
|
|
|
(void) callback_function->Call(Nan::GetCurrentContext(), Nan::Undefined(), 0, nullptr);
|
|
|
|
recorder->Reset();
|
|
call->Reset();
|
|
}).option_destroyed_execute(true);
|
|
|
|
std::thread([_async_callback, input, device]{
|
|
input->set_device(device);
|
|
_async_callback();
|
|
}).detach();
|
|
}
|
|
|
|
NAN_METHOD(AudioRecorderWrapper::_start) {
|
|
if(info.Length() != 1) {
|
|
Nan::ThrowError("missing callback");
|
|
return;
|
|
}
|
|
|
|
if(!info[0]->IsFunction()) {
|
|
Nan::ThrowError("not a function");
|
|
return;
|
|
}
|
|
|
|
auto input = ObjectWrap::Unwrap<AudioRecorderWrapper>(info.Holder())->_input;
|
|
std::string error{};
|
|
|
|
v8::Local<v8::Value> argv[1];
|
|
if(input->record(error))
|
|
argv[0] = Nan::New<v8::Boolean>(true);
|
|
else
|
|
argv[0] = Nan::LocalString(error);
|
|
(void) info[0].As<v8::Function>()->Call(Nan::GetCurrentContext(), Nan::Undefined(), 1, argv);
|
|
}
|
|
|
|
NAN_METHOD(AudioRecorderWrapper::_started) {
|
|
auto handle = ObjectWrap::Unwrap<AudioRecorderWrapper>(info.Holder());
|
|
auto input = handle->_input;
|
|
|
|
info.GetReturnValue().Set(input->recording());
|
|
}
|
|
|
|
NAN_METHOD(AudioRecorderWrapper::_stop) {
|
|
auto handle = ObjectWrap::Unwrap<AudioRecorderWrapper>(info.Holder());
|
|
auto input = handle->_input;
|
|
|
|
input->stop();
|
|
}
|
|
|
|
NAN_METHOD(AudioRecorderWrapper::_create_consumer) {
|
|
auto handle = ObjectWrap::Unwrap<AudioRecorderWrapper>(info.Holder());
|
|
auto consumer = handle->create_consumer();
|
|
|
|
if(!consumer) {
|
|
Nan::ThrowError("failed to create consumer");
|
|
return;
|
|
}
|
|
|
|
info.GetReturnValue().Set(consumer->handle());
|
|
}
|
|
|
|
NAN_METHOD(AudioRecorderWrapper::_get_consumers) {
|
|
auto handle = ObjectWrap::Unwrap<AudioRecorderWrapper>(info.Holder());
|
|
auto consumers = handle->consumers();
|
|
|
|
auto result = Nan::New<v8::Array>((uint32_t) consumers.size());
|
|
|
|
for(uint32_t index = 0; index < consumers.size(); index++)
|
|
Nan::Set(result, index, consumers[index]->handle());
|
|
|
|
info.GetReturnValue().Set(result);
|
|
}
|
|
|
|
NAN_METHOD(AudioRecorderWrapper::_delete_consumer) {
|
|
auto handle = ObjectWrap::Unwrap<AudioRecorderWrapper>(info.Holder());
|
|
|
|
if(info.Length() != 1 || !info[0]->IsObject()) {
|
|
Nan::ThrowError("invalid argument");
|
|
return;
|
|
}
|
|
|
|
if(!Nan::New(AudioConsumerWrapper::constructor_template())->HasInstance(info[0])) {
|
|
Nan::ThrowError("invalid consumer");
|
|
return;
|
|
}
|
|
|
|
auto consumer = ObjectWrap::Unwrap<AudioConsumerWrapper>(info[0]->ToObject(Nan::GetCurrentContext()).ToLocalChecked());
|
|
handle->delete_consumer(consumer);
|
|
}
|
|
|
|
NAN_METHOD(AudioRecorderWrapper::_set_volume) {
|
|
auto handle = ObjectWrap::Unwrap<AudioRecorderWrapper>(info.Holder());
|
|
|
|
if(info.Length() != 1 || !info[0]->IsNumber()) {
|
|
Nan::ThrowError("invalid argument");
|
|
return;
|
|
}
|
|
|
|
handle->_input->set_volume((float) info[0]->NumberValue(Nan::GetCurrentContext()).FromMaybe(0));
|
|
}
|
|
|
|
NAN_METHOD(AudioRecorderWrapper::_get_volume) {
|
|
auto handle = ObjectWrap::Unwrap<AudioRecorderWrapper>(info.Holder());
|
|
info.GetReturnValue().Set(handle->_input->volume());
|
|
} |