#include "VoiceConnection.h" #include "VoiceClient.h" #include "../ServerConnection.h" #include "AudioSender.h" #include "../../audio/js/AudioConsumer.h" #include "../../audio/AudioInput.h" #include "../../logger.h" #include /* MUST be included as last file */ using namespace std; using namespace tc; using namespace tc::connection; using namespace ts; using namespace ts::protocol; using namespace audio::recorder; VoiceConnectionWrap::VoiceConnectionWrap(const std::shared_ptr& handle) : handle(handle) {} VoiceConnectionWrap::~VoiceConnectionWrap() { if(!this->_voice_recoder_handle.IsEmpty()) { auto old_consumer = this->_voice_recoder_ptr; assert(old_consumer); lock_guard read_lock(old_consumer->native_consumer()->on_read_lock); old_consumer->native_consumer()->on_read = nullptr; } } void VoiceConnectionWrap::do_wrap(const v8::Local &object) { this->Wrap(object); } NAN_MODULE_INIT(VoiceConnectionWrap::Init) { auto klass = Nan::New(VoiceConnectionWrap::NewInstance); klass->SetClassName(Nan::New("VoiceConnection").ToLocalChecked()); klass->InstanceTemplate()->SetInternalFieldCount(1); Nan::SetPrototypeMethod(klass, "decoding_supported", VoiceConnectionWrap::_decoding_supported); Nan::SetPrototypeMethod(klass, "encoding_supported", VoiceConnectionWrap::_encoding_supported); Nan::SetPrototypeMethod(klass, "register_client", VoiceConnectionWrap::_register_client); Nan::SetPrototypeMethod(klass, "available_clients", VoiceConnectionWrap::_available_clients); Nan::SetPrototypeMethod(klass, "unregister_client", VoiceConnectionWrap::_unregister_client); Nan::SetPrototypeMethod(klass, "audio_source", VoiceConnectionWrap::_audio_source); Nan::SetPrototypeMethod(klass, "set_audio_source", VoiceConnectionWrap::_set_audio_source); constructor().Reset(Nan::GetFunction(klass).ToLocalChecked()); } NAN_METHOD(VoiceConnectionWrap::NewInstance) { if(!info.IsConstructCall()) Nan::ThrowError("invalid invoke!"); } NAN_METHOD(VoiceConnectionWrap::_connected) { info.GetReturnValue().Set(true); } NAN_METHOD(VoiceConnectionWrap::_encoding_supported) { if(info.Length() != 1) { Nan::ThrowError("invalid argument count"); return; } auto codec = info[0]->Uint32Value(Nan::GetCurrentContext()).FromMaybe(0); info.GetReturnValue().Set(codec >= 4 && codec <= 5); /* ignore SPEX currently :/ */ } NAN_METHOD(VoiceConnectionWrap::_decoding_supported) { if(info.Length() != 1) { Nan::ThrowError("invalid argument count"); return; } auto codec = info[0]->Uint32Value(Nan::GetCurrentContext()).FromMaybe(0); info.GetReturnValue().Set(codec >= 4 && codec <= 5); /* ignore SPEX currently :/ */ } NAN_METHOD(VoiceConnectionWrap::_register_client) { return ObjectWrap::Unwrap(info.Holder())->register_client(info); } NAN_METHOD(VoiceConnectionWrap::register_client) { if(info.Length() != 1) { Nan::ThrowError("invalid argument count"); return; } auto id = info[0]->Uint32Value(Nan::GetCurrentContext()).FromMaybe(0); auto handle = this->handle.lock(); if(!handle) { Nan::ThrowError("handle has been deallocated"); return; } auto client = handle->register_client(id); if(!client) { Nan::ThrowError("failed to register client"); return; } client->initialize_js_object(); info.GetReturnValue().Set(client->js_handle()); } NAN_METHOD(VoiceConnectionWrap::_available_clients) { return ObjectWrap::Unwrap(info.Holder())->available_clients(info); } NAN_METHOD(VoiceConnectionWrap::available_clients) { auto handle = this->handle.lock(); if(!handle) { Nan::ThrowError("handle has been deallocated"); return; } auto client = handle->clients(); v8::Local result = Nan::New(client.size()); for(size_t index = 0; index < client.size(); index++) Nan::Set(result, index, client[index]->js_handle()); info.GetReturnValue().Set(result); } NAN_METHOD(VoiceConnectionWrap::_unregister_client) { return ObjectWrap::Unwrap(info.Holder())->unregister_client(info); } NAN_METHOD(VoiceConnectionWrap::unregister_client) { if(info.Length() != 1) { Nan::ThrowError("invalid argument count"); return; } auto id = info[0]->Uint32Value(Nan::GetCurrentContext()).FromMaybe(0); auto handle = this->handle.lock(); if(!handle) { Nan::ThrowError("handle has been deallocated"); return; } auto client = handle->find_client(id); if(!client) { Nan::ThrowError("missing client"); return; } client->finalize_js_object(); handle->delete_client(client); } NAN_METHOD(VoiceConnectionWrap::_audio_source) { auto client = ObjectWrap::Unwrap(info.Holder()); if(info.Length() != 1) { Nan::ThrowError("invalid argument count"); return; } info.GetReturnValue().Set(client->_voice_recoder_handle.Get(info.GetIsolate())); } NAN_METHOD(VoiceConnectionWrap::_set_audio_source) { return ObjectWrap::Unwrap(info.Holder())->set_audio_source(info); } NAN_METHOD(VoiceConnectionWrap::set_audio_source) { if(info.Length() != 1) { Nan::ThrowError("invalid argument count"); return; } if(!Nan::New(AudioConsumerWrapper::constructor_template())->HasInstance(info[0]) && !info[0]->IsNullOrUndefined()) { Nan::ThrowError("invalid consumer (Consumer must be native!)"); return; } if(!this->handle.lock()) { Nan::ThrowError("handle has been deallocated"); return; } this->release_recorder(); if(!info[0]->IsNullOrUndefined()) { this->_voice_recoder_ptr = ObjectWrap::Unwrap(info[0]->ToObject(Nan::GetCurrentContext()).ToLocalChecked()); this->_voice_recoder_handle.Reset(info[0]->ToObject(Nan::GetCurrentContext()).ToLocalChecked()); auto native_consumer = this->_voice_recoder_ptr->native_consumer(); weak_ptr weak_handle = this->handle; auto sample_rate = native_consumer->sample_rate; auto channels = native_consumer->channel_count; lock_guard read_lock(this->_voice_recoder_ptr->native_read_callback_lock); this->_voice_recoder_ptr->native_read_callback = [weak_handle, sample_rate, channels](const void* buffer, size_t length) { auto handle = weak_handle.lock(); if(!handle) return; shared_ptr sender = handle->voice_sender(); if(sender) { if(length > 0 && buffer) sender->send_data(buffer, length, sample_rate, channels); else sender->send_stop(); } }; } } void VoiceConnectionWrap::release_recorder() { if(!this->_voice_recoder_handle.IsEmpty()) { assert(v8::Isolate::GetCurrent()); auto old_consumer = this->_voice_recoder_ptr; assert(old_consumer); lock_guard read_lock(this->_voice_recoder_ptr->native_read_callback_lock); this->_voice_recoder_ptr->native_read_callback = nullptr; } else { assert(!this->_voice_recoder_ptr); } this->_voice_recoder_ptr = nullptr; this->_voice_recoder_handle.Reset(); } VoiceConnection::VoiceConnection(ServerConnection *handle) : _handle(handle) { this->_voice_sender = make_shared(this); this->_voice_sender->_ref = this->_voice_sender; this->_voice_sender->set_codec(codec::OPUS_MUSIC); } VoiceConnection::~VoiceConnection() { if(v8::Isolate::GetCurrent()) this->finalize_js_object(); else { assert(this->_js_handle.IsEmpty()); } this->_voice_sender->finalize(); } void VoiceConnection::reset() { lock_guard lock(this->_clients_lock); this->_clients.clear(); } void VoiceConnection::initialize_js_object() { auto object_wrap = new VoiceConnectionWrap(this->ref()); auto object = Nan::NewInstance(Nan::New(VoiceConnectionWrap::constructor()), 0, nullptr).ToLocalChecked(); object_wrap->do_wrap(object); this->_js_handle.Reset(Nan::GetCurrentContext()->GetIsolate(), object); } void VoiceConnection::finalize_js_object() { this->_js_handle.Reset(); } std::shared_ptr VoiceConnection::find_client(uint16_t client_id) { lock_guard lock(this->_clients_lock); for(const auto& client : this->_clients) if(client->client_id() == client_id) return client; return nullptr; } std::shared_ptr VoiceConnection::register_client(uint16_t client_id) { lock_guard lock(this->_clients_lock); auto client = this->find_client(client_id); if(client) return client; client = make_shared(this->ref(), client_id); client->_ref = client; this->_clients.push_back(client); return client; } void VoiceConnection::delete_client(const std::shared_ptr &client) { { lock_guard lock(this->_clients_lock); auto it = find(this->_clients.begin(), this->_clients.end(), client); if(it != this->_clients.end()) { this->_clients.erase(it); } } //TODO deinitialize client } void VoiceConnection::process_packet(const std::shared_ptr &packet) { if(packet->type() == PacketTypeInfo::Voice) { if(packet->data().length() < 5) { //TODO log invalid voice packet return; } auto packet_id = be2le16(&packet->data()[0]); auto client_id = be2le16(&packet->data()[2]); auto codec_id = (uint8_t) packet->data()[4]; auto flag_head = packet->has_flag(PacketFlag::Compressed); //container->voice_data = packet->data().length() > 5 ? packet->data().range(5) : pipes::buffer{}; //log_info(category::voice_connection, tr("Received voice packet from {}. Packet ID: {}"), client_id, packet_id); auto client = this->find_client(client_id); if(!client) { log_warn(category::voice_connection, tr("Received voice packet from unknown client {}. Dropping packet!"), client_id); return; } if(packet->data().length() > 5) client->process_packet(packet_id, packet->data().range(5), (codec::value) codec_id, flag_head); else client->process_packet(packet_id, pipes::buffer_view{nullptr, 0}, (codec::value) codec_id, flag_head); } else { //TODO implement whisper } }