316 lines
9.7 KiB
C++
Raw Normal View History

2019-06-26 22:09:01 +02:00
#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 <misc/endianness.h> /* 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<VoiceConnection>& 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<v8::Object> &object) {
this->Wrap(object);
}
NAN_MODULE_INIT(VoiceConnectionWrap::Init) {
auto klass = Nan::New<v8::FunctionTemplate>(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);
2019-06-26 22:09:01 +02:00
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);
2019-06-26 22:09:01 +02:00
info.GetReturnValue().Set(codec >= 4 && codec <= 5); /* ignore SPEX currently :/ */
}
NAN_METHOD(VoiceConnectionWrap::_register_client) {
return ObjectWrap::Unwrap<VoiceConnectionWrap>(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);
2019-06-26 22:09:01 +02:00
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<VoiceConnectionWrap>(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<v8::Array> result = Nan::New<v8::Array>(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<VoiceConnectionWrap>(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);
2019-06-26 22:09:01 +02:00
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<VoiceConnectionWrap>(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<VoiceConnectionWrap>(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<audio::recorder::AudioConsumerWrapper>(info[0]->ToObject(Nan::GetCurrentContext()).ToLocalChecked());
this->_voice_recoder_handle.Reset(info[0]->ToObject(Nan::GetCurrentContext()).ToLocalChecked());
2019-06-26 22:09:01 +02:00
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<VoiceSender> 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<VoiceSender>(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<VoiceClient> 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<VoiceClient> 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<VoiceClient>(this->ref(), client_id);
client->_ref = client;
this->_clients.push_back(client);
return client;
}
void VoiceConnection::delete_client(const std::shared_ptr<tc::connection::VoiceClient> &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<ts::protocol::ServerPacket> &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];
2019-07-05 21:36:28 +02:00
auto flag_head = packet->has_flag(PacketFlag::Compressed);
2019-06-26 22:09:01 +02:00
//container->voice_data = packet->data().length() > 5 ? packet->data().range(5) : pipes::buffer{};
2019-08-21 10:00:27 +02:00
//log_info(category::voice_connection, tr("Received voice packet from {}. Packet ID: {}"), client_id, packet_id);
2019-06-26 22:09:01 +02:00
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)
2019-06-30 17:24:10 +02:00
client->process_packet(packet_id, packet->data().range(5), (codec::value) codec_id, flag_head);
2019-06-26 22:09:01 +02:00
else
2019-06-30 17:24:10 +02:00
client->process_packet(packet_id, pipes::buffer_view{nullptr, 0}, (codec::value) codec_id, flag_head);
2019-06-26 22:09:01 +02:00
} else {
//TODO implement whisper
}
}