#include #include #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("create_recorder").ToLocalChecked(), Nan::GetFunction(Nan::New(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(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(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 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 AudioRecorderWrapper::create_consumer() { auto result = shared_ptr(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 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 &obj) { this->Wrap(obj); } NAN_METHOD(AudioRecorderWrapper::_get_device) { auto handle = ObjectWrap::Unwrap(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(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> _callback = make_unique>(info[1].As()); unique_ptr> _recorder = make_unique>(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(info.Holder())->_input; std::string error{}; v8::Local argv[1]; if(input->record(error)) argv[0] = Nan::New(true); else argv[0] = Nan::LocalString(error); (void) info[0].As()->Call(Nan::GetCurrentContext(), Nan::Undefined(), 1, argv); } NAN_METHOD(AudioRecorderWrapper::_started) { auto handle = ObjectWrap::Unwrap(info.Holder()); auto input = handle->_input; info.GetReturnValue().Set(input->recording()); } NAN_METHOD(AudioRecorderWrapper::_stop) { auto handle = ObjectWrap::Unwrap(info.Holder()); auto input = handle->_input; input->stop(); } NAN_METHOD(AudioRecorderWrapper::_create_consumer) { auto handle = ObjectWrap::Unwrap(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(info.Holder()); auto consumers = handle->consumers(); auto result = Nan::New((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(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(info[0]->ToObject(Nan::GetCurrentContext()).ToLocalChecked()); handle->delete_consumer(consumer); } NAN_METHOD(AudioRecorderWrapper::_set_volume) { auto handle = ObjectWrap::Unwrap(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(info.Holder()); info.GetReturnValue().Set(handle->_input->volume()); }