215 lines
7.3 KiB
C++
215 lines
7.3 KiB
C++
#pragma once
|
|
|
|
#include <array>
|
|
#include <nan.h>
|
|
#include <include/NanEventCallback.h>
|
|
#include <functional>
|
|
#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;
|
|
|
|
namespace codec {
|
|
enum value {
|
|
MIN = 0,
|
|
|
|
SPEEX_NARROWBAND = 0,
|
|
SPEEX_WIDEBAND = 1,
|
|
SPEEX_ULTRA_WIDEBAND = 2,
|
|
|
|
CELT_MONO = 3,
|
|
|
|
OPUS_VOICE = 4,
|
|
OPUS_MUSIC = 5,
|
|
|
|
MAX = 5,
|
|
};
|
|
|
|
struct CodecInfo {
|
|
bool supported;
|
|
std::string name;
|
|
std::function<std::shared_ptr<audio::codec::Converter>(std::string&)> new_converter;
|
|
};
|
|
|
|
extern const CodecInfo info[6];
|
|
inline const CodecInfo* get_info(value codec) {
|
|
if(codec > value::MAX || codec < value::MIN) {
|
|
return nullptr;
|
|
}
|
|
return &info[codec];
|
|
}
|
|
}
|
|
|
|
class VoiceClient : private event::EventEntry {
|
|
friend class VoiceConnection;
|
|
|
|
#ifdef WIN32
|
|
template<typename _Tp, typename _Up>
|
|
friend _NODISCARD std::shared_ptr<_Tp> std::static_pointer_cast(std::shared_ptr<_Up>&& _Other) noexcept;
|
|
#else
|
|
template<typename _Tp, typename _Up>
|
|
friend inline std::shared_ptr<_Tp> std::static_pointer_cast(const std::shared_ptr<_Up>& __r) noexcept;
|
|
#endif
|
|
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 */, codec::value /* codec */, 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};
|
|
bool reset_decoder{false};
|
|
|
|
std::chrono::system_clock::time_point receive_timestamp;
|
|
|
|
pipes::buffer buffer;
|
|
codec::value codec{codec::MIN};
|
|
|
|
uint16_t packet_id{0};
|
|
EncodedBuffer* next{nullptr};
|
|
};
|
|
|
|
struct AudioCodec {
|
|
enum struct State {
|
|
UNINITIALIZED,
|
|
INITIALIZED_SUCCESSFULLY,
|
|
INITIALIZED_FAIL,
|
|
UNSUPPORTED,
|
|
};
|
|
|
|
codec::value codec{};
|
|
|
|
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{};
|
|
|
|
[[nodiscard]] inline std::chrono::system_clock::time_point stream_timeout() const {
|
|
return this->last_packet_timestamp + std::chrono::milliseconds{1000};
|
|
}
|
|
|
|
State state{State::UNINITIALIZED};
|
|
std::shared_ptr<audio::codec::Converter> converter{nullptr};
|
|
std::shared_ptr<audio::AudioResampler> resampler{nullptr};
|
|
|
|
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};
|
|
};
|
|
|
|
std::array<AudioCodec, codec::MAX + 1> codec{};
|
|
void initialize_code(const codec::value& /* codec */);
|
|
|
|
/* 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};
|
|
|
|
std::chrono::system_clock::time_point _last_received_packet{};
|
|
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;
|
|
void event_execute_dropped(const std::chrono::system_clock::time_point &point) override;
|
|
|
|
bool handle_output_underflow(size_t sample_count);
|
|
|
|
/* its recommend to call this in correct packet oder */
|
|
std::shared_ptr<audio::SampleBuffer> decode_buffer(const codec::value& /* codec */,const pipes::buffer_view& /* buffer */, bool /* fec */);
|
|
};
|
|
|
|
|
|
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_();
|
|
};
|
|
} |