TeaSpeak-Client/native/serverconnection/src/connection/audio/VoiceClient.h

174 lines
6.2 KiB
C++

#pragma once
#include <array>
#include <nan.h>
#include <include/NanEventCallback.h>
#include <functional>
#include <optional>
#include <pipes/buffer.h>
#include "../../audio/AudioResampler.h"
#include "../../audio/codec/Converter.h"
#include "../../audio/AudioOutput.h"
#include "../../EventLoop.h"
#include "../../logger.h"
namespace tc::connection {
class ServerConnection;
class VoiceConnection;
class VoiceClient;
class VoiceClient : public event::EventEntry {
friend class VoiceConnection;
public:
struct state {
enum value {
buffering, /* this state is never active */
playing,
stopping,
stopped
};
constexpr static std::array names = {
"buffering",
"playing",
"stopping",
"stopped"
};
};
VoiceClient(const std::shared_ptr<VoiceConnection>& /* connection */, uint16_t /* client id */);
virtual ~VoiceClient();
void initialize();
[[nodiscard]] inline uint16_t client_id() const { return this->client_id_; }
void initialize_js_object();
void finalize_js_object();
v8::Local<v8::Object> js_handle() {
assert(v8::Isolate::GetCurrent());
return this->js_handle_.Get(Nan::GetCurrentContext()->GetIsolate());
}
inline std::shared_ptr<VoiceClient> ref() { return this->ref_.lock(); }
void process_packet(uint16_t packet_id, const pipes::buffer_view& /* buffer */, uint8_t /* payload codec id */, bool /* head */);
void execute_tick();
inline float get_volume() const { return this->volume_; }
inline void set_volume(float value) { this->volume_ = value; }
inline state::value state() { return this->state_; }
void cancel_replay();
std::function<void()> on_state_changed;
inline std::shared_ptr<audio::AudioOutputSource> output_stream() { return this->output_source; }
private:
struct EncodedBuffer {
bool head{false};
std::chrono::system_clock::time_point receive_timestamp;
pipes::buffer buffer;
uint8_t codec{0xFF};
uint16_t packet_id{0};
EncodedBuffer* next{nullptr};
};
struct {
uint16_t last_packet_id{0xFFFF}; /* the first packet id is 0 so one packet before is 0xFFFF */
std::chrono::system_clock::time_point last_packet_timestamp{};
std::mutex pending_lock{};
EncodedBuffer* pending_buffers{nullptr};
/* forces all packets which are within the next chain to replay until (inclusive) force_replay is reached */
EncodedBuffer* force_replay{nullptr};
bool process_pending{false};
[[nodiscard]] inline std::chrono::system_clock::time_point stream_timeout() const {
return this->last_packet_timestamp + std::chrono::milliseconds{1000};
}
} packet_queue;
struct {
/* the decoder has been initialized with fec data */
bool decoder_initialized{false};
audio::codec::AudioCodec current_codec{audio::codec::AudioCodec::Unknown};
std::unique_ptr<audio::codec::AudioDecoder> decoder{};
std::unique_ptr<audio::AudioResampler> resampler{};
} decoder;
/* might be null (if audio hasn't been initialized) */
std::shared_ptr<audio::AudioOutputSource> output_source{};
std::weak_ptr<VoiceClient> ref_{};
v8::Persistent<v8::Object> js_handle_{};
uint16_t client_id_{0};
float volume_{1.f};
state::value state_{state::stopped};
inline void set_state(state::value value) {
if(value == this->state_) {
return;
}
log_warn(category::audio, tr("Client {} state changed from {} to {}"), this->client_id_, state::names[this->state_], state::names[value]);
this->state_ = value;
if(this->on_state_changed) {
this->on_state_changed();
}
}
/* Call only within the event loop or when execute lock is locked */
void drop_enqueued_buffers();
void event_execute(const std::chrono::system_clock::time_point &point) override;
bool handle_output_underflow(size_t sample_count);
/**
* Reset the decoder.
*/
void reset_decoder(bool /* deallocate */);
/**
* Decode and playback an audio packet.
* If fec decode is active we try to decode the fec data within the packet and playback them instead of the packet data.
* Note: If fec is set and the decoder hasn't been initialized we'll drop the buffer.
*/
void playback_audio_packet(uint8_t /* codec protocol id */, const void* /* buffer */, size_t /* buffer length */, bool /* use fec data */);
};
class VoiceClientWrap : public Nan::ObjectWrap {
public:
static NAN_MODULE_INIT(Init);
static NAN_METHOD(NewInstance);
static inline Nan::Persistent<v8::Function> & constructor() {
static Nan::Persistent<v8::Function> my_constructor;
return my_constructor;
}
explicit VoiceClientWrap(const std::shared_ptr<VoiceClient>&);
~VoiceClientWrap() override;
void do_wrap(const v8::Local<v8::Object>&);
private:
static NAN_METHOD(_get_state);
static NAN_METHOD(_get_volume);
static NAN_METHOD(_set_volume);
static NAN_METHOD(_abort_replay);
static NAN_METHOD(_get_stream);
std::weak_ptr<VoiceClient> _handle;
bool currently_playing_{false};
Nan::callback_t<> call_state_changed;
void call_state_changed_();
};
}