mirror of
https://github.com/QuasarApp/SoundBand.git
synced 2025-05-04 02:59:34 +00:00
add chronoTime
This commit is contained in:
parent
d69006103e
commit
17dabbaf6b
@ -32,7 +32,8 @@ SOURCES += \
|
|||||||
../sync/song.cpp \
|
../sync/song.cpp \
|
||||||
../sync/sync.cpp \
|
../sync/sync.cpp \
|
||||||
servermodel.cpp \
|
servermodel.cpp \
|
||||||
songmodel.cpp
|
songmodel.cpp \
|
||||||
|
../sync/chronotime.cpp
|
||||||
|
|
||||||
HEADERS += \
|
HEADERS += \
|
||||||
mainwindow.h \
|
mainwindow.h \
|
||||||
@ -44,7 +45,8 @@ HEADERS += \
|
|||||||
../sync/song.h \
|
../sync/song.h \
|
||||||
../sync/sync.h \
|
../sync/sync.h \
|
||||||
servermodel.h \
|
servermodel.h \
|
||||||
songmodel.h
|
songmodel.h \
|
||||||
|
../sync/chronotime.h
|
||||||
|
|
||||||
FORMS += \
|
FORMS += \
|
||||||
mainwindow.ui
|
mainwindow.ui
|
||||||
|
@ -1,6 +1,5 @@
|
|||||||
#include "ETcpSocket.h"
|
#include "ETcpSocket.h"
|
||||||
#include "exaptions.h"
|
#include "exaptions.h"
|
||||||
#include "config.h"
|
|
||||||
|
|
||||||
ETcpSocket::ETcpSocket()
|
ETcpSocket::ETcpSocket()
|
||||||
{
|
{
|
||||||
@ -23,7 +22,10 @@ ETcpSocket::ETcpSocket(const QString& address, int port){
|
|||||||
}
|
}
|
||||||
|
|
||||||
void ETcpSocket::init(){
|
void ETcpSocket::init(){
|
||||||
array=new QByteArray;
|
array = new QByteArray;
|
||||||
|
fSynced = false;
|
||||||
|
differenceTime = 0;
|
||||||
|
|
||||||
connect(source,SIGNAL(connected()),this,SLOT(connected_()));
|
connect(source,SIGNAL(connected()),this,SLOT(connected_()));
|
||||||
connect(source,SIGNAL(disconnected()),this,SLOT(disconnected_()));
|
connect(source,SIGNAL(disconnected()),this,SLOT(disconnected_()));
|
||||||
connect(source,SIGNAL(error(QAbstractSocket::SocketError)),this,SLOT(error_(QAbstractSocket::SocketError)));
|
connect(source,SIGNAL(error(QAbstractSocket::SocketError)),this,SLOT(error_(QAbstractSocket::SocketError)));
|
||||||
@ -59,11 +61,77 @@ void ETcpSocket::stateChanged_(QAbstractSocket::SocketState socketState){
|
|||||||
|
|
||||||
void ETcpSocket::readReady_(){
|
void ETcpSocket::readReady_(){
|
||||||
bool sizewrite=array->isEmpty();
|
bool sizewrite=array->isEmpty();
|
||||||
//while(source->bytesAvailable())
|
|
||||||
array->append(source->readAll());
|
array->append(source->readAll());
|
||||||
QDataStream stream(array,QIODevice::ReadOnly);
|
QDataStream stream(array,QIODevice::ReadOnly);
|
||||||
if(sizewrite)
|
if(sizewrite){
|
||||||
stream>>size;
|
stream >> size;
|
||||||
|
switch (size) {
|
||||||
|
case CALIBRATION_SENDER:{
|
||||||
|
milliseconds ms;
|
||||||
|
stream >> ms;
|
||||||
|
|
||||||
|
milliseconds range = ChronoTime::now() - ms;
|
||||||
|
|
||||||
|
QByteArray cArray;
|
||||||
|
QDataStream stream(&cArray,QIODevice::ReadWrite);
|
||||||
|
|
||||||
|
if(range < MIN_DIFFERENCE){
|
||||||
|
stream << CALIBRATION_RECEIVER_DONE;
|
||||||
|
fSynced = true;
|
||||||
|
|
||||||
|
}else{
|
||||||
|
stream << CALIBRATION_RECEIVER;
|
||||||
|
stream << range;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
source->write(cArray);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
case CALIBRATION_RECEIVER:{
|
||||||
|
ping = ChronoTime::now() - lastTime;
|
||||||
|
milliseconds ms;
|
||||||
|
stream >> ms;
|
||||||
|
|
||||||
|
differenceTime += ms + ping / 2;
|
||||||
|
|
||||||
|
QByteArray cArray;
|
||||||
|
QDataStream stream(&cArray,QIODevice::ReadWrite);
|
||||||
|
stream << CALIBRATION_SENDER;
|
||||||
|
stream << milliseconds(ChronoTime::now() + differenceTime);
|
||||||
|
|
||||||
|
lastTime = ChronoTime::now();
|
||||||
|
source->write(cArray);
|
||||||
|
return;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
case CALIBRATION_RECEIVER_DONE:{
|
||||||
|
|
||||||
|
QByteArray cArray;
|
||||||
|
QDataStream stream(&cArray,QIODevice::ReadWrite);
|
||||||
|
stream << CALIBRATION_SENDER_DONE;
|
||||||
|
|
||||||
|
stream << milliseconds(-differenceTime);
|
||||||
|
fSynced = true;
|
||||||
|
|
||||||
|
source->write(cArray);
|
||||||
|
return;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
case CALIBRATION_SENDER_DONE:{
|
||||||
|
|
||||||
|
stream >> differenceTime;
|
||||||
|
|
||||||
|
return;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
#ifdef QT_DEBUG
|
#ifdef QT_DEBUG
|
||||||
qDebug()<<"messae size:"<<size;
|
qDebug()<<"messae size:"<<size;
|
||||||
qDebug()<<"message package size:"<<array->size();
|
qDebug()<<"message package size:"<<array->size();
|
||||||
@ -77,7 +145,6 @@ void ETcpSocket::readReady_(){
|
|||||||
}else{
|
}else{
|
||||||
emit donwload(array->size(),size);
|
emit donwload(array->size(),size);
|
||||||
}
|
}
|
||||||
// emit ReadReady(this);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
QString ETcpSocket::name() const{
|
QString ETcpSocket::name() const{
|
||||||
@ -112,12 +179,21 @@ QString ETcpSocket::toStringTcp(){
|
|||||||
return source->peerAddress().toString();
|
return source->peerAddress().toString();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
milliseconds ETcpSocket::getDifferenceTime() const{
|
||||||
|
return differenceTime;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool ETcpSocket::isSynced()const{
|
||||||
|
return fSynced;
|
||||||
|
}
|
||||||
|
|
||||||
bool ETcpSocket::Write(const QByteArray&data){
|
bool ETcpSocket::Write(const QByteArray&data){
|
||||||
if(source->state()==QTcpSocket::ConnectedState){
|
if(source->state()==QTcpSocket::ConnectedState){
|
||||||
QByteArray array;
|
QByteArray array;
|
||||||
QDataStream stream(&array,QIODevice::ReadWrite);
|
QDataStream stream(&array,QIODevice::ReadWrite);
|
||||||
stream<<qint32(0);
|
|
||||||
//stream<<data;
|
stream << qint32(0);
|
||||||
|
|
||||||
array.append(data);
|
array.append(data);
|
||||||
stream.device()->seek(0);
|
stream.device()->seek(0);
|
||||||
stream<<qint32(array.size());
|
stream<<qint32(array.size());
|
||||||
@ -133,6 +209,19 @@ bool ETcpSocket::Write(const QByteArray&data){
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void ETcpSocket::calibration(){
|
||||||
|
if(!fSynced){
|
||||||
|
QByteArray cArray;
|
||||||
|
QDataStream stream(&cArray,QIODevice::ReadWrite);
|
||||||
|
stream << CALIBRATION_SENDER;
|
||||||
|
stream << milliseconds(ChronoTime::now());
|
||||||
|
lastTime = ChronoTime::now();
|
||||||
|
|
||||||
|
source->write(cArray);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
ETcpSocket::~ETcpSocket()
|
ETcpSocket::~ETcpSocket()
|
||||||
{
|
{
|
||||||
for(QByteArray*i:ReadyStack){
|
for(QByteArray*i:ReadyStack){
|
||||||
|
@ -4,6 +4,14 @@
|
|||||||
#include <QTcpServer>
|
#include <QTcpServer>
|
||||||
#include <QList>
|
#include <QList>
|
||||||
#include <QDataStream>
|
#include <QDataStream>
|
||||||
|
#include "chronotime.h"
|
||||||
|
|
||||||
|
#define CALIBRATION_SENDER qint32(-1)
|
||||||
|
#define CALIBRATION_RECEIVER qint32(-2)
|
||||||
|
#define CALIBRATION_SENDER_DONE qint32(-3)
|
||||||
|
#define CALIBRATION_RECEIVER_DONE qint32(-4)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief The ETcpSocket class
|
* @brief The ETcpSocket class
|
||||||
@ -25,6 +33,8 @@
|
|||||||
* }
|
* }
|
||||||
*
|
*
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
|
||||||
class ETcpSocket:public QObject
|
class ETcpSocket:public QObject
|
||||||
{
|
{
|
||||||
Q_OBJECT
|
Q_OBJECT
|
||||||
@ -35,6 +45,17 @@ private:
|
|||||||
qint32 size;
|
qint32 size;
|
||||||
QList<QByteArray*> ReadyStack;
|
QList<QByteArray*> ReadyStack;
|
||||||
void init();
|
void init();
|
||||||
|
/**
|
||||||
|
* @brief differenceTime - local time difference in milliseconds
|
||||||
|
* on milliseconds
|
||||||
|
*/
|
||||||
|
milliseconds differenceTime;
|
||||||
|
/**
|
||||||
|
* @brief fSynced - whether a time difference was found.
|
||||||
|
*/
|
||||||
|
bool fSynced;
|
||||||
|
milliseconds lastTime;
|
||||||
|
milliseconds ping;
|
||||||
|
|
||||||
private slots:
|
private slots:
|
||||||
void connected_();
|
void connected_();
|
||||||
@ -70,6 +91,25 @@ public:
|
|||||||
* @return true if all done else false.
|
* @return true if all done else false.
|
||||||
*/
|
*/
|
||||||
bool Write(const QByteArray&);
|
bool Write(const QByteArray&);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief getDifferenceTime
|
||||||
|
* @return Difference Time of remoute node.
|
||||||
|
*/
|
||||||
|
milliseconds getDifferenceTime()const;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief isSynced
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
bool isSynced()const;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief calibration - will calculate the difference in time between the connected node
|
||||||
|
* and the current node.
|
||||||
|
*/
|
||||||
|
void calibration();
|
||||||
|
|
||||||
~ETcpSocket();
|
~ETcpSocket();
|
||||||
public slots:
|
public slots:
|
||||||
/**
|
/**
|
||||||
@ -126,6 +166,7 @@ signals:
|
|||||||
*/
|
*/
|
||||||
void StateChanged(ETcpSocket*,QAbstractSocket::SocketState socketState);
|
void StateChanged(ETcpSocket*,QAbstractSocket::SocketState socketState);
|
||||||
|
|
||||||
|
|
||||||
};
|
};
|
||||||
|
|
||||||
#endif // CLIENT_H
|
#endif // CLIENT_H
|
||||||
|
31
sync/chronotime.cpp
Normal file
31
sync/chronotime.cpp
Normal file
@ -0,0 +1,31 @@
|
|||||||
|
#include "chronotime.h"
|
||||||
|
#include <QDebug>
|
||||||
|
|
||||||
|
ChronoTime::ChronoTime()
|
||||||
|
{
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* information about chrono
|
||||||
|
* https://stackoverflow.com/questions/31255486/c-how-do-i-convert-a-stdchronotime-point-to-long-and-back
|
||||||
|
*/
|
||||||
|
|
||||||
|
milliseconds ChronoTime::now(){
|
||||||
|
auto tim = std::chrono::system_clock::now();
|
||||||
|
auto mc = std::chrono::time_point_cast<std::chrono::milliseconds>(tim);
|
||||||
|
auto epoh = mc.time_since_epoch();
|
||||||
|
#ifdef QT_DEBUG
|
||||||
|
qDebug() << epoh.count();
|
||||||
|
#endif
|
||||||
|
return epoh.count();
|
||||||
|
}
|
||||||
|
|
||||||
|
Clock ChronoTime::from(const milliseconds& mc){
|
||||||
|
std::chrono::milliseconds dur(mc);
|
||||||
|
return Clock(dur);
|
||||||
|
}
|
||||||
|
|
||||||
|
milliseconds ChronoTime::abs(milliseconds number){
|
||||||
|
return (number << 1) >> 1;
|
||||||
|
}
|
35
sync/chronotime.h
Normal file
35
sync/chronotime.h
Normal file
@ -0,0 +1,35 @@
|
|||||||
|
#ifndef CHRONOTIME_H
|
||||||
|
#define CHRONOTIME_H
|
||||||
|
#include <chrono>
|
||||||
|
#include "config.h"
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Time_point on nanosecunds (uint64_t)
|
||||||
|
*/
|
||||||
|
typedef long long milliseconds;
|
||||||
|
typedef std::chrono::time_point<std::chrono::high_resolution_clock> Clock;
|
||||||
|
|
||||||
|
class ChronoTime
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
ChronoTime();
|
||||||
|
/**
|
||||||
|
* @brief now - get now time on microsecunds
|
||||||
|
* @return - count of microsecunds
|
||||||
|
*/
|
||||||
|
static milliseconds now();
|
||||||
|
/**
|
||||||
|
* @brief from cast to chrono secunds
|
||||||
|
* @param mcrs microseconds of uint_64
|
||||||
|
* @return microseconds of chrono
|
||||||
|
*/
|
||||||
|
static Clock from(const milliseconds &mcrs);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief abs
|
||||||
|
* @return module of numver
|
||||||
|
*/
|
||||||
|
static milliseconds abs(milliseconds number);
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif // CHRONOTIME_H
|
@ -20,6 +20,6 @@
|
|||||||
#define DEEP_SCANER_INTERVAL 10000 // 10 sec
|
#define DEEP_SCANER_INTERVAL 10000 // 10 sec
|
||||||
|
|
||||||
// sync
|
// sync
|
||||||
#define MIN_DIFFERENCE 100 // millisec
|
#define MIN_DIFFERENCE 10 // millisec
|
||||||
|
|
||||||
#endif // CONFIG_H
|
#endif // CONFIG_H
|
||||||
|
@ -71,12 +71,4 @@ public:
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
class feedbackError:public std::exception
|
|
||||||
{
|
|
||||||
public:
|
|
||||||
QString what(){
|
|
||||||
return QObject::tr("Could not create synchronization confirmation packet.");
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
#endif // EXAPTIONS_H
|
#endif // EXAPTIONS_H
|
||||||
|
@ -3,12 +3,7 @@
|
|||||||
#include <QString>
|
#include <QString>
|
||||||
#include <QByteArray>
|
#include <QByteArray>
|
||||||
#include <QDataStream>
|
#include <QDataStream>
|
||||||
#include "config.h"
|
#include "chronotime.h"
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief Time_point on nanosecunds (uint64_t)
|
|
||||||
*/
|
|
||||||
typedef quint64 milliseconds;
|
|
||||||
|
|
||||||
namespace syncLib {
|
namespace syncLib {
|
||||||
|
|
||||||
@ -23,7 +18,7 @@ struct Syncer
|
|||||||
*/
|
*/
|
||||||
milliseconds seek;
|
milliseconds seek;
|
||||||
/**
|
/**
|
||||||
* @brief run when is play media file (int)
|
* @brief run when is play media file (milliseconds)
|
||||||
*/
|
*/
|
||||||
milliseconds run;
|
milliseconds run;
|
||||||
};
|
};
|
||||||
|
@ -41,10 +41,6 @@ Sync::Sync(const QString address, int port, const QString &datadir):
|
|||||||
connect(player, SIGNAL(stateChanged(QMediaPlayer::State)), SLOT(endPlay(QMediaPlayer::State)));
|
connect(player, SIGNAL(stateChanged(QMediaPlayer::State)), SLOT(endPlay(QMediaPlayer::State)));
|
||||||
}
|
}
|
||||||
|
|
||||||
int Sync::abs(int number) const{
|
|
||||||
return (number << 1) >> 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool Sync::findHeader(const Song &song){
|
bool Sync::findHeader(const Song &song){
|
||||||
|
|
||||||
for(SongHeader & header: playList){
|
for(SongHeader & header: playList){
|
||||||
@ -179,26 +175,6 @@ bool Sync::load(const SongHeader &song,Song &result){
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
|
||||||
* information about chrono
|
|
||||||
* https://stackoverflow.com/questions/31255486/c-how-do-i-convert-a-stdchronotime-point-to-long-and-back
|
|
||||||
*/
|
|
||||||
|
|
||||||
milliseconds Sync::now(){
|
|
||||||
auto tim = std::chrono::system_clock::now();
|
|
||||||
auto mc = std::chrono::time_point_cast<std::chrono::milliseconds>(tim);
|
|
||||||
auto epoh = mc.time_since_epoch();
|
|
||||||
#ifdef QT_DEBUG
|
|
||||||
qDebug() << epoh.count();
|
|
||||||
#endif
|
|
||||||
return epoh.count();
|
|
||||||
}
|
|
||||||
|
|
||||||
Clock Sync::from(const milliseconds& mc){
|
|
||||||
std::chrono::milliseconds dur(mc);
|
|
||||||
return Clock(dur);
|
|
||||||
}
|
|
||||||
|
|
||||||
bool Sync::play(const SongHeader &header, const Syncer *syncdata){
|
bool Sync::play(const SongHeader &header, const Syncer *syncdata){
|
||||||
|
|
||||||
if(!header.isValid()){
|
if(!header.isValid()){
|
||||||
@ -296,13 +272,16 @@ void Sync::jump(const qint64 seek){
|
|||||||
}
|
}
|
||||||
|
|
||||||
bool Sync::sync(const Syncer &sync){
|
bool Sync::sync(const Syncer &sync){
|
||||||
milliseconds sync_time = sync.run - now();
|
milliseconds sync_time = sync.run - ChronoTime::now();
|
||||||
if(sync_time > MAX_SYNC_TIME && sync_time <= 0)
|
if(sync_time > MAX_SYNC_TIME && sync_time <= 0)
|
||||||
return false;
|
return false;
|
||||||
Clock run_time = from(sync.run);
|
|
||||||
|
Clock run_time = ChronoTime::from(sync.run);
|
||||||
|
|
||||||
do {
|
do {
|
||||||
std::this_thread::yield();
|
std::this_thread::yield();
|
||||||
} while (std::chrono::high_resolution_clock::now() < run_time);
|
} while (std::chrono::high_resolution_clock::now() < run_time);
|
||||||
|
|
||||||
player->setPosition(sync.seek);
|
player->setPosition(sync.seek);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
@ -348,14 +327,14 @@ bool Sync::createPackage(Type type, package &pac){
|
|||||||
|
|
||||||
if(type & TypePackage::t_sync && fbroadcaster){
|
if(type & TypePackage::t_sync && fbroadcaster){
|
||||||
|
|
||||||
pac.playdata.run = now() + SYNC_TIME;
|
pac.playdata.run = ChronoTime::now() + SYNC_TIME;
|
||||||
pac.playdata.seek = player->position() + SYNC_TIME;
|
pac.playdata.seek = player->position() + SYNC_TIME;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if( type & TypePackage::t_feedback && !fbroadcaster){
|
if( type & TypePackage::t_feedback && !fbroadcaster){
|
||||||
|
|
||||||
pac.playdata.run = now();
|
pac.playdata.run = ChronoTime::now();
|
||||||
pac.playdata.seek = player->position();
|
pac.playdata.seek = player->position();
|
||||||
|
|
||||||
}
|
}
|
||||||
@ -467,7 +446,7 @@ void Sync::packageRender(ETcpSocket *socket){
|
|||||||
return ;
|
return ;
|
||||||
}
|
}
|
||||||
|
|
||||||
unsigned int diff = abs(static_cast<unsigned int>(player->position() - (pkg.getPlayData().seek + (now() - pkg.getPlayData().run))));
|
unsigned int diff = ChronoTime::abs(player->position() - (pkg.getPlayData().seek + (ChronoTime::now() - pkg.getPlayData().run)));
|
||||||
|
|
||||||
#ifdef QT_DEBUG
|
#ifdef QT_DEBUG
|
||||||
qDebug() << "diff " << socket->name() <<": " << diff;
|
qDebug() << "diff " << socket->name() <<": " << diff;
|
||||||
|
18
sync/sync.h
18
sync/sync.h
@ -13,7 +13,6 @@ class QSqlQuery;
|
|||||||
class QBuffer;
|
class QBuffer;
|
||||||
namespace syncLib {
|
namespace syncLib {
|
||||||
|
|
||||||
typedef std::chrono::time_point<std::chrono::high_resolution_clock> Clock;
|
|
||||||
|
|
||||||
class Node;
|
class Node;
|
||||||
|
|
||||||
@ -37,11 +36,6 @@ private:
|
|||||||
LocalScanner deepScaner;
|
LocalScanner deepScaner;
|
||||||
int port;
|
int port;
|
||||||
QString dataBaseName;
|
QString dataBaseName;
|
||||||
/**
|
|
||||||
* @brief abs
|
|
||||||
* @return module of numver
|
|
||||||
*/
|
|
||||||
int abs(int number)const;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief findHeader set curent song if playList have playng song
|
* @brief findHeader set curent song if playList have playng song
|
||||||
@ -87,17 +81,7 @@ private:
|
|||||||
* @return song drom local database.
|
* @return song drom local database.
|
||||||
*/
|
*/
|
||||||
Song fromDataBase(const int id);
|
Song fromDataBase(const int id);
|
||||||
/**
|
|
||||||
* @brief now - get now time on microsecunds
|
|
||||||
* @return - count of microsecunds
|
|
||||||
*/
|
|
||||||
milliseconds now();
|
|
||||||
/**
|
|
||||||
* @brief from cast to chrono secunds
|
|
||||||
* @param mcrs microseconds of uint_64
|
|
||||||
* @return microseconds of chrono
|
|
||||||
*/
|
|
||||||
Clock from(const milliseconds &mcrs);
|
|
||||||
/**
|
/**
|
||||||
* @brief createPackage - Create a package that shows current state of the node
|
* @brief createPackage - Create a package that shows current state of the node
|
||||||
* @param type - Type of an answer
|
* @param type - Type of an answer
|
||||||
|
Loading…
x
Reference in New Issue
Block a user