Supporting voice whisper again
This commit is contained in:
@@ -14,6 +14,7 @@
|
||||
#include "StringVariable.h"
|
||||
#include "misc/timer.h"
|
||||
#include "../manager/ActionLogger.h"
|
||||
#include "./voice/VoiceClient.h"
|
||||
|
||||
using namespace std::chrono;
|
||||
using namespace ts;
|
||||
@@ -26,7 +27,7 @@ constexpr static auto kMaxWhisperClientNameLength{30};
|
||||
constexpr static auto kWhisperClientUniqueIdLength{28}; /* base64 encoded SHA1 hash */
|
||||
constexpr static auto kWhisperMaxHeaderLength{2 + 2 + 1 + 2 + kWhisperClientUniqueIdLength + 1 + kMaxWhisperClientNameLength};
|
||||
|
||||
SpeakingClient::SpeakingClient(sql::SqlManager *a, const std::shared_ptr<VirtualServer> &b) : ConnectedClient(a, b) {
|
||||
SpeakingClient::SpeakingClient(sql::SqlManager *a, const std::shared_ptr<VirtualServer> &b) : ConnectedClient(a, b), whisper_handler_{this} {
|
||||
speak_begin = std::chrono::system_clock::now();
|
||||
speak_last_packet = std::chrono::system_clock::now();
|
||||
};
|
||||
@@ -68,29 +69,6 @@ bool SpeakingClient::should_handle_voice_packet(size_t) {
|
||||
return true;
|
||||
}
|
||||
|
||||
//2 + 2 + 8
|
||||
#define OUT_WHISPER_PKT_OFFSET 5
|
||||
//#define PKT_LOG_WHISPER
|
||||
|
||||
enum WhisperType {
|
||||
SERVER_GROUP = 0,
|
||||
CHANNEL_GROUP = 1,
|
||||
CHANNEL_COMMANDER = 2,
|
||||
ALL = 3,
|
||||
|
||||
ECHO = 0x10,
|
||||
};
|
||||
|
||||
enum WhisperTarget {
|
||||
CHANNEL_ALL = 0,
|
||||
CHANNEL_CURRENT = 1,
|
||||
CHANNEL_PARENT = 2,
|
||||
CHANNEL_ALL_PARENT = 3,
|
||||
CHANNEL_FAMILY = 4,
|
||||
CHANNEL_COMPLETE_FAMILY = 5,
|
||||
CHANNEL_SUBCHANNELS = 6
|
||||
};
|
||||
|
||||
inline bool update_whisper_error(std::chrono::system_clock::time_point& last) {
|
||||
auto now = std::chrono::system_clock::now();
|
||||
if(last + std::chrono::milliseconds{500} < now) {
|
||||
@@ -100,277 +78,6 @@ inline bool update_whisper_error(std::chrono::system_clock::time_point& last) {
|
||||
return false;
|
||||
}
|
||||
|
||||
//All clients => type := SERVER_GROUP and target_id := 0
|
||||
//Server group => type := SERVER_GROUP and target_id := <server group id>
|
||||
//Channel group => type := CHANNEL_GROUP and target_id := <channel group id>
|
||||
//Channel commander => type := CHANNEL_COMMANDER and target_id := 0
|
||||
void SpeakingClient::handlePacketVoiceWhisper(const pipes::buffer_view& payload, bool new_packet, bool head) {
|
||||
if(payload.length() < 5) {
|
||||
this->disconnect("Invalid packet (Voice whisper)");
|
||||
logMessage(this->getServerId(), "{} Tried to send a too short whisper packet. Length: {}", CLIENT_STR_LOG_PREFIX, payload.length());
|
||||
return;
|
||||
}
|
||||
|
||||
uint16_t payload_offset{0};
|
||||
auto voice_packet_id = be2le16((char*) payload.data_ptr(), payload_offset, &payload_offset);
|
||||
auto voice_codec = (uint8_t) payload[payload_offset++];
|
||||
|
||||
std::deque<std::shared_ptr<SpeakingClient>> target_clients;
|
||||
|
||||
if(new_packet) {
|
||||
if(payload.length() < 7) {
|
||||
this->disconnect("Invalid packet (Voice whisper | New)");
|
||||
logMessage(this->getServerId(), "{} Tried to send a too short whisper packet. Length: {}", CLIENT_STR_LOG_PREFIX, payload.length());
|
||||
return;
|
||||
}
|
||||
|
||||
auto type = (WhisperType) payload[payload_offset++];
|
||||
auto target = (WhisperTarget) payload[payload_offset++];
|
||||
auto type_id = be2le64((char*) payload.data_ptr(), payload_offset, &payload_offset);
|
||||
|
||||
#ifdef PKT_LOG_WHISPER
|
||||
logTrace(this->getServerId(), "{} Whisper data length: {}. Type: {}. Target: {}. Target ID: {}.", CLIENT_STR_LOG_PREFIX, data_length, type, target, type_id);
|
||||
#endif
|
||||
if(type == WhisperType::ECHO) {
|
||||
target_clients.push_back(dynamic_pointer_cast<SpeakingClient>(this->ref()));
|
||||
} else {
|
||||
for(const auto& client : this->server->getClients()) {
|
||||
auto speakingClient = dynamic_pointer_cast<SpeakingClient>(client);
|
||||
if(!speakingClient || client == this) continue;
|
||||
if(!speakingClient->currentChannel) continue;
|
||||
|
||||
if(type == WhisperType::ALL) {
|
||||
target_clients.push_back(speakingClient);
|
||||
} else if(type == WhisperType::SERVER_GROUP) {
|
||||
if(type_id == 0)
|
||||
target_clients.push_back(speakingClient);
|
||||
else {
|
||||
shared_lock client_lock(this->channel_lock);
|
||||
for(const auto& id : client->cached_server_groups) {
|
||||
if(id == type_id) {
|
||||
target_clients.push_back(speakingClient);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
} else if(type == WhisperType::CHANNEL_GROUP) {
|
||||
if(client->cached_channel_group == type_id)
|
||||
target_clients.push_back(speakingClient);
|
||||
} else if(type == WhisperType::CHANNEL_COMMANDER) {
|
||||
if(client->properties()[property::CLIENT_IS_CHANNEL_COMMANDER].as<bool>())
|
||||
target_clients.push_back(speakingClient);
|
||||
}
|
||||
}
|
||||
|
||||
if(target == WhisperTarget::CHANNEL_CURRENT) {
|
||||
target_clients.erase(std::remove_if(target_clients.begin(), target_clients.end(), [&](const shared_ptr<SpeakingClient>& target) {
|
||||
return target->currentChannel != this->currentChannel;
|
||||
}), target_clients.end());
|
||||
} else if(target == WhisperTarget::CHANNEL_PARENT) {
|
||||
auto current_parent = this->currentChannel->parent();
|
||||
if(!current_parent) return;
|
||||
|
||||
target_clients.erase(std::remove_if(target_clients.begin(), target_clients.end(), [&](const shared_ptr<SpeakingClient>& target) {
|
||||
return target->currentChannel != current_parent;
|
||||
}), target_clients.end());
|
||||
} else if(target == WhisperTarget::CHANNEL_ALL_PARENT) {
|
||||
shared_ptr<BasicChannel> current_parent;
|
||||
{
|
||||
current_parent = this->currentChannel->parent();
|
||||
if(!current_parent) return;
|
||||
}
|
||||
target_clients.erase(std::remove_if(target_clients.begin(), target_clients.end(), [&](const shared_ptr<SpeakingClient>& target) {
|
||||
auto tmp_parent = current_parent;
|
||||
while(tmp_parent && tmp_parent != target->currentChannel)
|
||||
tmp_parent = tmp_parent->parent();
|
||||
return target->currentChannel != tmp_parent;
|
||||
}), target_clients.end());
|
||||
} else if(target == WhisperTarget::CHANNEL_FAMILY) {
|
||||
shared_ptr<BasicChannel> current = this->currentChannel;
|
||||
target_clients.erase(std::remove_if(target_clients.begin(), target_clients.end(), [&](const shared_ptr<SpeakingClient>& target) {
|
||||
auto tmp_current = target->currentChannel;
|
||||
while(tmp_current && tmp_current != current)
|
||||
tmp_current = tmp_current->parent();
|
||||
return current != tmp_current;
|
||||
}), target_clients.end());
|
||||
} else if(target == WhisperTarget::CHANNEL_COMPLETE_FAMILY) {
|
||||
shared_ptr<BasicChannel> current = this->currentChannel;
|
||||
while(current && current->parent()) current = current->parent();
|
||||
target_clients.erase(std::remove_if(target_clients.begin(), target_clients.end(), [&](const shared_ptr<SpeakingClient>& target) {
|
||||
auto tmp_current = target->currentChannel;
|
||||
while(tmp_current && tmp_current != current)
|
||||
tmp_current = tmp_current->parent();
|
||||
return current != tmp_current;
|
||||
}), target_clients.end());
|
||||
} else if(target == WhisperTarget::CHANNEL_SUBCHANNELS) {
|
||||
shared_ptr<BasicChannel> current = this->currentChannel;
|
||||
target_clients.erase(std::remove_if(target_clients.begin(), target_clients.end(), [&](const shared_ptr<SpeakingClient>& target) {
|
||||
return target->currentChannel->parent() != current;
|
||||
}), target_clients.end());
|
||||
}
|
||||
|
||||
auto self_lock = this->_this.lock();
|
||||
target_clients.erase(std::remove_if(target_clients.begin(), target_clients.end(), [&](const std::shared_ptr<ConnectedClient>& cl) {
|
||||
auto speakingClient = dynamic_pointer_cast<SpeakingClient>(cl);
|
||||
return !speakingClient->shouldReceiveVoiceWhisper(self_lock);
|
||||
}), target_clients.end());
|
||||
|
||||
if(target_clients.empty()) {
|
||||
if(update_whisper_error(this->speak_last_no_whisper_target)) {
|
||||
command_result result{error::whisper_no_targets};
|
||||
this->notifyError(result);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
if(target_clients.size() > this->server->properties()[property::VIRTUALSERVER_MIN_CLIENTS_IN_CHANNEL_BEFORE_FORCED_SILENCE].as_save<size_t>()) {
|
||||
if(update_whisper_error(this->speak_last_too_many_whisper_targets)) {
|
||||
command_result result{error::whisper_too_many_targets};
|
||||
this->notifyError(result);
|
||||
}
|
||||
return;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
auto channelCount = (uint8_t) payload[payload_offset++];
|
||||
auto clientCount = (uint8_t) payload[payload_offset++];
|
||||
if(payload.length() < 5 + clientCount * 2 + channelCount * 8) {
|
||||
logMessage(this->getServerId(), "{} Tried to send a too short whisper packet. Length: {} Required: {}", CLIENT_STR_LOG_PREFIX, payload.length(), to_string(5 + channelCount * 2 + clientCount * 8));
|
||||
return;
|
||||
}
|
||||
|
||||
ChannelId channelIds[channelCount];
|
||||
ClientId clientIds[clientCount];
|
||||
|
||||
for(uint8_t index = 0; index < channelCount; index++) {
|
||||
channelIds[index] = be2le64((char*) payload.data_ptr(), payload_offset, &payload_offset);
|
||||
}
|
||||
|
||||
for(uint8_t index = 0; index < clientCount; index++) {
|
||||
clientIds[index] = be2le16((char*) payload.data_ptr(), payload_offset, &payload_offset);
|
||||
}
|
||||
|
||||
#ifdef PKT_LOG_WHISPER
|
||||
logTrace(this->getServerId(), "{} Whisper data length: {}. Client count: {}. Channel count: {}.", CLIENT_STR_LOG_PREFIX, dataLength, clientCount, channelCount);
|
||||
#endif
|
||||
|
||||
for(const auto& client : this->server->getClients()) {
|
||||
auto speaking_client = dynamic_pointer_cast<SpeakingClient>(client);
|
||||
if(!speaking_client || client == this || !speaking_client->currentChannel)
|
||||
continue;
|
||||
|
||||
auto clientChannelId = speaking_client->getChannelId();
|
||||
auto clientId = speaking_client->getClientId();
|
||||
|
||||
for(uint8_t index = 0; index < channelCount; index++) {
|
||||
if(channelIds[index] == clientChannelId) {
|
||||
goto add_client;
|
||||
}
|
||||
}
|
||||
|
||||
for(uint8_t index = 0; index < clientCount; index++) {
|
||||
if(clientIds[index] == clientId) {
|
||||
goto add_client;
|
||||
}
|
||||
}
|
||||
|
||||
continue;
|
||||
|
||||
add_client:
|
||||
if(!speaking_client->shouldReceiveVoiceWhisper(this->ref())) {
|
||||
continue;
|
||||
}
|
||||
|
||||
target_clients.push_back(speaking_client);
|
||||
}
|
||||
}
|
||||
|
||||
if(target_clients.empty()) {
|
||||
if(update_whisper_error(this->speak_last_no_whisper_target)) {
|
||||
command_result result{error::whisper_no_targets};
|
||||
this->notifyError(result);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
if(target_clients.size() > this->server->properties()[property::VIRTUALSERVER_MIN_CLIENTS_IN_CHANNEL_BEFORE_FORCED_SILENCE].as_save<size_t>()) {
|
||||
if(update_whisper_error(this->speak_last_too_many_whisper_targets)) {
|
||||
command_result result{error::whisper_too_many_targets};
|
||||
this->notifyError(result);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
/* send the packet */
|
||||
{
|
||||
size_t voice_payload_length = payload.length() - payload_offset;
|
||||
|
||||
//Create the packet data
|
||||
char whisper_packet_buffer[kWhisperMaxHeaderLength + voice_payload_length];
|
||||
size_t whisper_packet_offset{0};
|
||||
size_t whisper_packet_teamspeak_offset{0};
|
||||
|
||||
/* writing the teaspeak header */
|
||||
if(head) {
|
||||
auto uniqueId = this->getUid();
|
||||
auto nickname = this->getDisplayName();
|
||||
|
||||
if(uniqueId.length() > kWhisperClientUniqueIdLength) {
|
||||
logCritical(LOG_GENERAL, "Clients unique id is longer than the expected max length of {}. Unique length: {}", kWhisperClientUniqueIdLength, uniqueId.length());
|
||||
return;
|
||||
}
|
||||
|
||||
if(nickname.length() > kMaxWhisperClientNameLength) {
|
||||
logCritical(LOG_GENERAL, "Clients name is longer than the expected max length of {}. Name length: {}", kMaxWhisperClientNameLength, nickname.length());
|
||||
return;
|
||||
}
|
||||
|
||||
memset(whisper_packet_buffer + whisper_packet_offset, 0, kWhisperClientUniqueIdLength);
|
||||
memcpy(whisper_packet_buffer + whisper_packet_offset, uniqueId.data(), uniqueId.length());
|
||||
whisper_packet_offset += kWhisperClientUniqueIdLength;
|
||||
|
||||
whisper_packet_buffer[whisper_packet_offset++] = nickname.length();
|
||||
memcpy(whisper_packet_buffer + whisper_packet_offset, nickname.data(), nickname.length());
|
||||
whisper_packet_offset += nickname.length();
|
||||
}
|
||||
|
||||
/* writing the "normal" header and payload */
|
||||
{
|
||||
whisper_packet_teamspeak_offset = whisper_packet_offset;
|
||||
|
||||
*(uint16_t*) &whisper_packet_buffer[whisper_packet_offset] = htons(voice_packet_id);
|
||||
whisper_packet_offset += 2;
|
||||
|
||||
*(uint16_t*) &whisper_packet_buffer[whisper_packet_offset] = htons(this->getClientId());
|
||||
whisper_packet_offset += 2;
|
||||
|
||||
whisper_packet_buffer[whisper_packet_offset++] = voice_codec;
|
||||
|
||||
if(voice_payload_length > 0) {
|
||||
memcpy(&whisper_packet_buffer[whisper_packet_offset], &payload[payload_offset], voice_payload_length);
|
||||
whisper_packet_offset += voice_payload_length;
|
||||
}
|
||||
}
|
||||
|
||||
VoicePacketFlags flags{};
|
||||
flags.head = head;
|
||||
|
||||
pipes::buffer_view teaspeak_packet{}, teamspeak_packet{};
|
||||
teaspeak_packet = pipes::buffer_view{whisper_packet_buffer, whisper_packet_offset};
|
||||
teamspeak_packet = pipes::buffer_view{whisper_packet_buffer + whisper_packet_teamspeak_offset, whisper_packet_offset - whisper_packet_teamspeak_offset};
|
||||
|
||||
auto self_ref = this->ref();
|
||||
for(const auto& cl : target_clients) {
|
||||
if(cl == self_ref || cl->shouldReceiveVoiceWhisper(self_ref)) {
|
||||
cl->send_voice_whisper_packet(teamspeak_packet, teaspeak_packet, flags);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
this->resetIdleTime();
|
||||
this->updateSpeak(false, std::chrono::system_clock::now());
|
||||
}
|
||||
|
||||
#define TEST_PARM(type) \
|
||||
do {\
|
||||
if(!cmd[0][key].castable<type>())\
|
||||
@@ -704,26 +411,28 @@ void SpeakingClient::processJoin() {
|
||||
|
||||
TIMING_STEP(timings, "setup ");
|
||||
ref_server->registerClient(_this.lock());
|
||||
if(this->rtc_client_id) {
|
||||
/* in case of client reconnect */
|
||||
this->server->rtc_server().destroy_client(this->rtc_client_id);
|
||||
}
|
||||
if(this->getType() == ClientType::CLIENT_TEAMSPEAK) {
|
||||
this->rtc_client_id = this->server->rtc_server().create_native_client(dynamic_pointer_cast<SpeakingClient>(this->ref()));
|
||||
} else if(this->getType() == ClientType::CLIENT_TEASPEAK) {
|
||||
/* TODO: Will be a RTP client later on, just without audio */
|
||||
this->rtc_client_id = this->server->rtc_server().create_native_client(dynamic_pointer_cast<SpeakingClient>(this->ref()));
|
||||
} else if(this->getType() == ClientType::CLIENT_WEB) {
|
||||
std::string error;
|
||||
auto result = this->server->rtc_server().create_rtp_client(dynamic_pointer_cast<SpeakingClient>(this->ref()), error);
|
||||
if(result > 0) {
|
||||
this->rtc_client_id = result;
|
||||
} else {
|
||||
this->rtc_client_id = 0;
|
||||
logCritical(this->getServerId(), "{} Failed to configure RTC session: {}", CLIENT_STR_LOG_PREFIX, error);
|
||||
{
|
||||
if(this->rtc_client_id) {
|
||||
/* in case of client reconnect */
|
||||
this->server->rtc_server().destroy_client(this->rtc_client_id);
|
||||
}
|
||||
|
||||
std::string error{};
|
||||
this->rtc_client_id = this->server->rtc_server().create_client(dynamic_pointer_cast<SpeakingClient>(this->ref()));
|
||||
|
||||
if(auto voice_client{dynamic_cast<VoiceClient*>(this)}; voice_client) {
|
||||
if(!this->server->rtc_server().initialize_native_connection(error, this->rtc_client_id)) {
|
||||
logCritical(this->getServerId(), "{} Native connection setup failed: {}", CLIENT_STR_LOG_PREFIX, error);
|
||||
}
|
||||
}
|
||||
if(this->getType() == ClientType::CLIENT_WEB || this->getType() == ClientType::CLIENT_TEASPEAK) {
|
||||
if(!this->server->rtc_server().initialize_rtc_connection(error, this->rtc_client_id)) {
|
||||
logCritical(this->getServerId(), "{} RTC connection setup failed: {}", CLIENT_STR_LOG_PREFIX, error);
|
||||
} else {
|
||||
this->rtc_session_pending_describe = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
this->rtc_session_pending_describe = true;
|
||||
|
||||
TIMING_STEP(timings, "server reg ");
|
||||
ref_server->getGroupManager()->cleanupAssignments(this->getClientDatabaseId());
|
||||
|
||||
Reference in New Issue
Block a user