mirror of
https://github.com/qTox/qTox.git
synced 2024-03-22 14:00:36 +08:00
Merge branch 'newav_final_for_realsies'
Implements the new toxav API.
This commit is contained in:
commit
5b036cca0f
7
qtox.pro
7
qtox.pro
|
@ -501,7 +501,8 @@ SOURCES += \
|
|||
src/widget/tool/movablewidget.cpp \
|
||||
src/widget/tool/micfeedbackwidget.cpp \
|
||||
src/widget/tool/removefrienddialog.cpp \
|
||||
src/video/groupnetcamview.cpp
|
||||
src/video/groupnetcamview.cpp \
|
||||
src/core/toxcall.cpp
|
||||
|
||||
HEADERS += \
|
||||
src/audio/audio.h \
|
||||
|
@ -552,4 +553,6 @@ HEADERS += \
|
|||
src/widget/tool/removefrienddialog.h \
|
||||
src/widget/tool/movablewidget.h \
|
||||
src/video/genericnetcamview.h \
|
||||
src/video/groupnetcamview.h
|
||||
src/video/groupnetcamview.h \
|
||||
src/core/indexedlist.h \
|
||||
src/core/toxcall.h
|
||||
|
|
|
@ -29,10 +29,13 @@
|
|||
#include "audio.h"
|
||||
#include "src/core/core.h"
|
||||
#include "src/persistence/settings.h"
|
||||
#include "src/core/coreav.h"
|
||||
|
||||
#include <QDebug>
|
||||
#include <QTimer>
|
||||
#include <QThread>
|
||||
#include <QMutexLocker>
|
||||
#include <QFile>
|
||||
|
||||
#include <cassert>
|
||||
|
||||
|
@ -110,18 +113,13 @@ void Audio::setOutputVolume(qreal volume)
|
|||
outputVolume = volume;
|
||||
alSourcef(alMainSource, AL_GAIN, outputVolume);
|
||||
|
||||
for (const ToxGroupCall& call : Core::groupCalls)
|
||||
for (const ToxGroupCall& call : CoreAV::groupCalls)
|
||||
{
|
||||
if (!call.active)
|
||||
continue;
|
||||
for (ALuint source : call.alSources)
|
||||
alSourcef(source, AL_GAIN, outputVolume);
|
||||
alSourcef(call.alSource, AL_GAIN, outputVolume);
|
||||
}
|
||||
|
||||
for (const ToxCall& call : Core::calls)
|
||||
for (const ToxFriendCall& call : CoreAV::calls)
|
||||
{
|
||||
if (!call.active)
|
||||
continue;
|
||||
alSourcef(call.alSource, AL_GAIN, outputVolume);
|
||||
}
|
||||
}
|
||||
|
@ -193,10 +191,11 @@ void Audio::openInput(const QString& inDevDescr)
|
|||
}
|
||||
alInDev = nullptr;
|
||||
|
||||
int stereoFlag = av_DefaultSettings.audio_channels==1 ? AL_FORMAT_MONO16 : AL_FORMAT_STEREO16;
|
||||
const uint32_t sampleRate = av_DefaultSettings.audio_sample_rate;
|
||||
const uint16_t frameDuration = av_DefaultSettings.audio_frame_duration;
|
||||
const uint32_t chnls = av_DefaultSettings.audio_channels;
|
||||
/// TODO: Try to actually detect if our audio source is stereo
|
||||
int stereoFlag = AUDIO_CHANNELS == 1 ? AL_FORMAT_MONO16 : AL_FORMAT_STEREO16;
|
||||
const uint32_t sampleRate = AUDIO_SAMPLE_RATE;
|
||||
const uint16_t frameDuration = AUDIO_FRAME_DURATION;
|
||||
const uint32_t chnls = AUDIO_CHANNELS;
|
||||
const ALCsizei bufSize = (frameDuration * sampleRate * 4) / 1000 * chnls;
|
||||
if (inDevDescr.isEmpty())
|
||||
alInDev = alcCaptureOpenDevice(nullptr, sampleRate, stereoFlag, bufSize);
|
||||
|
@ -211,7 +210,7 @@ void Audio::openInput(const QString& inDevDescr)
|
|||
|
||||
Core* core = Core::getInstance();
|
||||
if (core)
|
||||
core->resetCallSources(); // Force to regen each group call's sources
|
||||
core->getAv()->resetCallSources(); // Force to regen each group call's sources
|
||||
|
||||
// Restart the capture if necessary
|
||||
if (alInDev)
|
||||
|
@ -269,7 +268,7 @@ bool Audio::openOutput(const QString &outDevDescr)
|
|||
|
||||
Core* core = Core::getInstance();
|
||||
if (core)
|
||||
core->resetCallSources(); // Force to regen each group call's sources
|
||||
core->getAv()->resetCallSources(); // Force to regen each group call's sources
|
||||
|
||||
return true;
|
||||
}
|
||||
|
@ -366,12 +365,22 @@ void Audio::playMono16Sound(const QByteArray& data)
|
|||
alDeleteBuffers(1, &buffer);
|
||||
}
|
||||
|
||||
/**
|
||||
Play a 44100Hz mono 16bit PCM sound from a file
|
||||
*/
|
||||
void Audio::playMono16Sound(const char *path)
|
||||
{
|
||||
QFile sndFile(path);
|
||||
sndFile.open(QIODevice::ReadOnly);
|
||||
playMono16Sound(sndFile.readAll());
|
||||
}
|
||||
|
||||
/**
|
||||
@brief May be called from any thread, will always queue a call to playGroupAudio.
|
||||
|
||||
The first and last argument are ignored, but allow direct compatibility with toxcore.
|
||||
*/
|
||||
void Audio::playGroupAudioQueued(Tox*,int group, int peer, const int16_t* data,
|
||||
void Audio::playGroupAudioQueued(void*,int group, int peer, const int16_t* data,
|
||||
unsigned samples, uint8_t channels, unsigned sample_rate, void* core)
|
||||
{
|
||||
QMetaObject::invokeMethod(instance, "playGroupAudio", Qt::BlockingQueuedConnection,
|
||||
|
@ -391,15 +400,18 @@ void Audio::playGroupAudio(int group, int peer, const int16_t* data,
|
|||
assert(QThread::currentThread() == audioThread);
|
||||
QMutexLocker lock(&audioOutLock);
|
||||
|
||||
ToxGroupCall& call = Core::groupCalls[group];
|
||||
|
||||
if (!call.active || call.muteVol)
|
||||
if (!CoreAV::groupCalls.contains(group))
|
||||
return;
|
||||
|
||||
if (!call.alSources.contains(peer))
|
||||
ToxGroupCall& call = CoreAV::groupCalls[group];
|
||||
|
||||
if (call.inactive || call.muteVol)
|
||||
return;
|
||||
|
||||
if (!call.alSource)
|
||||
{
|
||||
alGenSources(1, &call.alSources[peer]);
|
||||
alSourcef(call.alSources[peer], AL_GAIN, outputVolume);
|
||||
alGenSources(1, &call.alSource);
|
||||
alSourcef(call.alSource, AL_GAIN, outputVolume);
|
||||
}
|
||||
|
||||
qreal volume = 0.;
|
||||
|
@ -409,14 +421,14 @@ void Audio::playGroupAudio(int group, int peer, const int16_t* data,
|
|||
|
||||
emit groupAudioPlayed(group, peer, volume / bufsize);
|
||||
|
||||
playAudioBuffer(call.alSources[peer], data, samples, channels, sample_rate);
|
||||
playAudioBuffer(call.alSource, data, samples, channels, sample_rate);
|
||||
}
|
||||
|
||||
void Audio::playAudioBuffer(ALuint alSource, const int16_t *data, int samples, unsigned channels, int sampleRate)
|
||||
{
|
||||
assert(channels == 1 || channels == 2);
|
||||
|
||||
QMutexLocker lock(&audioOutLock);
|
||||
QMutexLocker lock(&getInstance().audioOutLock);
|
||||
|
||||
ALuint bufid;
|
||||
ALint processed = 0, queued = 16;
|
||||
|
@ -447,7 +459,7 @@ void Audio::playAudioBuffer(ALuint alSource, const int16_t *data, int samples, u
|
|||
|
||||
ALint state;
|
||||
alGetSourcei(alSource, AL_SOURCE_STATE, &state);
|
||||
alSourcef(alSource, AL_GAIN, outputVolume);
|
||||
alSourcef(alSource, AL_GAIN, getInstance().outputVolume);
|
||||
if (state != AL_PLAYING)
|
||||
alSourcePlay(alSource);
|
||||
}
|
||||
|
@ -464,40 +476,63 @@ bool Audio::isInputReady()
|
|||
/**
|
||||
Returns true if the output device is open
|
||||
*/
|
||||
bool Audio::isOutputClosed()
|
||||
bool Audio::isOutputReady()
|
||||
{
|
||||
QMutexLocker locker(&audioOutLock);
|
||||
return alOutDev;
|
||||
}
|
||||
|
||||
void Audio::createSource(ALuint* source)
|
||||
{
|
||||
alGenSources(1, source);
|
||||
alSourcef(*source, AL_GAIN, getInstance().outputVolume);
|
||||
}
|
||||
|
||||
void Audio::deleteSource(ALuint* source)
|
||||
{
|
||||
if (alIsSource(*source))
|
||||
alDeleteSources(1, source);
|
||||
else
|
||||
qWarning() << "Trying to delete invalid audio source"<<*source;
|
||||
}
|
||||
|
||||
void Audio::startLoop()
|
||||
{
|
||||
alSourcei(alMainSource, AL_LOOPING, AL_TRUE);
|
||||
}
|
||||
|
||||
void Audio::stopLoop()
|
||||
{
|
||||
alSourcei(alMainSource, AL_LOOPING, AL_FALSE);
|
||||
alSourceStop(alMainSource);
|
||||
}
|
||||
|
||||
/**
|
||||
Does nothing and return false on failure
|
||||
*/
|
||||
bool Audio::tryCaptureSamples(uint8_t* buf, int framesize)
|
||||
bool Audio::tryCaptureSamples(int16_t* buf, int samples)
|
||||
{
|
||||
QMutexLocker lock(&audioInLock);
|
||||
|
||||
ALint samples=0;
|
||||
alcGetIntegerv(Audio::alInDev, ALC_CAPTURE_SAMPLES, sizeof(samples), &samples);
|
||||
if (samples < framesize)
|
||||
ALint curSamples=0;
|
||||
alcGetIntegerv(Audio::alInDev, ALC_CAPTURE_SAMPLES, sizeof(curSamples), &curSamples);
|
||||
if (curSamples < samples)
|
||||
return false;
|
||||
|
||||
memset(buf, 0, framesize * 2 * av_DefaultSettings.audio_channels); // Avoid uninitialized values (Valgrind)
|
||||
alcCaptureSamples(Audio::alInDev, buf, framesize);
|
||||
alcCaptureSamples(Audio::alInDev, buf, samples);
|
||||
|
||||
if (inputVolume != 1)
|
||||
{
|
||||
int16_t* bufReal = reinterpret_cast<int16_t*>(buf);
|
||||
for (int i = 0; i < framesize; ++i)
|
||||
for (size_t i = 0; i < samples * AUDIO_CHANNELS; ++i)
|
||||
{
|
||||
int sample = bufReal[i] * pow(inputVolume, 2);
|
||||
int sample = buf[i] * pow(inputVolume, 2);
|
||||
|
||||
if (sample < std::numeric_limits<int16_t>::min())
|
||||
sample = std::numeric_limits<int16_t>::min();
|
||||
else if (sample > std::numeric_limits<int16_t>::max())
|
||||
sample = std::numeric_limits<int16_t>::max();
|
||||
|
||||
bufReal[i] = sample;
|
||||
buf[i] = sample;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -22,6 +22,7 @@
|
|||
#define AUDIO_H
|
||||
|
||||
#include <QObject>
|
||||
#include <QHash>
|
||||
#include <QMutexLocker>
|
||||
#include <atomic>
|
||||
#include <cmath>
|
||||
|
@ -34,10 +35,20 @@
|
|||
#include <AL/alc.h>
|
||||
#endif
|
||||
|
||||
class QString;
|
||||
class QByteArray;
|
||||
class QTimer;
|
||||
class QThread;
|
||||
class QMutex;
|
||||
struct Tox;
|
||||
class AudioFilterer;
|
||||
|
||||
// Public default audio settings
|
||||
static constexpr uint32_t AUDIO_SAMPLE_RATE = 48000; ///< The next best Opus would take is 24k
|
||||
static constexpr uint32_t AUDIO_FRAME_DURATION = 20; ///< In milliseconds
|
||||
static constexpr uint32_t AUDIO_FRAME_SAMPLE_COUNT = AUDIO_FRAME_DURATION * AUDIO_SAMPLE_RATE/1000;
|
||||
static constexpr uint32_t AUDIO_CHANNELS = 2; ///< Ideally, we'd auto-detect, but that's a sane default
|
||||
|
||||
class Audio : public QObject
|
||||
{
|
||||
Q_OBJECT
|
||||
|
@ -59,12 +70,20 @@ public:
|
|||
bool openOutput(const QString& outDevDescr);
|
||||
|
||||
bool isInputReady();
|
||||
bool isOutputClosed();
|
||||
bool isOutputReady();
|
||||
|
||||
static void createSource(ALuint* source);
|
||||
static void deleteSource(ALuint* source);
|
||||
|
||||
void startLoop();
|
||||
void stopLoop();
|
||||
void playMono16Sound(const QByteArray& data);
|
||||
bool tryCaptureSamples(uint8_t* buf, int framesize);
|
||||
void playMono16Sound(const char* path);
|
||||
bool tryCaptureSamples(int16_t *buf, int samples);
|
||||
|
||||
static void playGroupAudioQueued(Tox*, int group, int peer, const int16_t* data,
|
||||
static void playAudioBuffer(ALuint alSource, const int16_t *data, int samples, unsigned channels, int sampleRate);
|
||||
|
||||
static void playGroupAudioQueued(void *, int group, int peer, const int16_t* data,
|
||||
unsigned samples, uint8_t channels, unsigned sample_rate, void*);
|
||||
|
||||
#ifdef QTOX_FILTER_AUDIO
|
||||
|
@ -85,8 +104,6 @@ private:
|
|||
Audio();
|
||||
~Audio();
|
||||
|
||||
void playAudioBuffer(ALuint alSource, const int16_t *data, int samples, unsigned channels, int sampleRate);
|
||||
|
||||
private:
|
||||
static Audio* instance;
|
||||
|
||||
|
|
|
@ -22,6 +22,7 @@
|
|||
#include "src/nexus.h"
|
||||
#include "src/core/cdata.h"
|
||||
#include "src/core/cstring.h"
|
||||
#include "src/core/coreav.h"
|
||||
#include "src/persistence/settings.h"
|
||||
#include "src/widget/gui.h"
|
||||
#include "src/persistence/historykeeper.h"
|
||||
|
@ -33,6 +34,7 @@
|
|||
#include "src/video/camerasource.h"
|
||||
|
||||
#include <tox/tox.h>
|
||||
#include <tox/toxav.h>
|
||||
|
||||
#include <ctime>
|
||||
#include <cassert>
|
||||
|
@ -54,40 +56,31 @@
|
|||
|
||||
const QString Core::CONFIG_FILE_NAME = "data";
|
||||
const QString Core::TOX_EXT = ".tox";
|
||||
QHash<int, ToxGroupCall> Core::groupCalls;
|
||||
QThread* Core::coreThread{nullptr};
|
||||
|
||||
#define MAX_GROUP_MESSAGE_LEN 1024
|
||||
|
||||
Core::Core(QThread *CoreThread, Profile& profile) :
|
||||
tox(nullptr), toxav(nullptr), profile(profile), ready{false}
|
||||
tox(nullptr), av(nullptr), profile(profile), ready{false}
|
||||
{
|
||||
coreThread = CoreThread;
|
||||
|
||||
Audio::getInstance();
|
||||
|
||||
videobuf = nullptr;
|
||||
|
||||
toxTimer = new QTimer(this);
|
||||
toxTimer->setSingleShot(true);
|
||||
connect(toxTimer, &QTimer::timeout, this, &Core::process);
|
||||
connect(&Settings::getInstance(), &Settings::dhtServerListChanged, this, &Core::process);
|
||||
|
||||
for (int i=0; i<TOXAV_MAX_CALLS;i++)
|
||||
{
|
||||
calls[i].active = false;
|
||||
calls[i].alSource = 0;
|
||||
calls[i].sendAudioTimer = new QTimer();
|
||||
calls[i].sendAudioTimer->moveToThread(coreThread);
|
||||
}
|
||||
}
|
||||
|
||||
void Core::deadifyTox()
|
||||
{
|
||||
if (toxav)
|
||||
if (av)
|
||||
{
|
||||
toxav_kill(toxav);
|
||||
toxav = nullptr;
|
||||
delete av;
|
||||
av = nullptr;
|
||||
}
|
||||
if (tox)
|
||||
{
|
||||
|
@ -113,18 +106,8 @@ Core::~Core()
|
|||
coreThread->wait(500);
|
||||
}
|
||||
|
||||
for (ToxCall call : calls)
|
||||
{
|
||||
if (!call.active)
|
||||
continue;
|
||||
hangupCall(call.callId);
|
||||
}
|
||||
|
||||
deadifyTox();
|
||||
|
||||
delete[] videobuf;
|
||||
videobuf=nullptr;
|
||||
|
||||
Audio& audio = Audio::getInstance();
|
||||
audio.closeInput();
|
||||
audio.closeOutput();
|
||||
|
@ -135,6 +118,11 @@ Core* Core::getInstance()
|
|||
return Nexus::getCore();
|
||||
}
|
||||
|
||||
CoreAV *Core::getAv()
|
||||
{
|
||||
return av;
|
||||
}
|
||||
|
||||
void Core::makeTox(QByteArray savedata)
|
||||
{
|
||||
// IPv6 needed for LAN discovery, but can crash some weird routers. On by default, can be disabled in options.
|
||||
|
@ -233,8 +221,8 @@ void Core::makeTox(QByteArray savedata)
|
|||
return;
|
||||
}
|
||||
|
||||
toxav = toxav_new(tox, TOXAV_MAX_CALLS);
|
||||
if (toxav == nullptr)
|
||||
av = new CoreAV(tox);
|
||||
if (av->getToxAv() == nullptr)
|
||||
{
|
||||
qCritical() << "Toxav core failed to start";
|
||||
emit failedToStart();
|
||||
|
@ -310,20 +298,6 @@ void Core::start()
|
|||
tox_callback_file_recv_chunk(tox, CoreFile::onFileRecvChunkCallback, this);
|
||||
tox_callback_file_recv_control(tox, CoreFile::onFileControlCallback, this);
|
||||
|
||||
toxav_register_callstate_callback(toxav, onAvInvite, av_OnInvite, this);
|
||||
toxav_register_callstate_callback(toxav, onAvStart, av_OnStart, this);
|
||||
toxav_register_callstate_callback(toxav, onAvCancel, av_OnCancel, this);
|
||||
toxav_register_callstate_callback(toxav, onAvReject, av_OnReject, this);
|
||||
toxav_register_callstate_callback(toxav, onAvEnd, av_OnEnd, this);
|
||||
toxav_register_callstate_callback(toxav, onAvRinging, av_OnRinging, this);
|
||||
toxav_register_callstate_callback(toxav, onAvMediaChange, av_OnPeerCSChange, this);
|
||||
toxav_register_callstate_callback(toxav, onAvMediaChange, av_OnSelfCSChange, this);
|
||||
toxav_register_callstate_callback(toxav, onAvRequestTimeout, av_OnRequestTimeout, this);
|
||||
toxav_register_callstate_callback(toxav, onAvPeerTimeout, av_OnPeerTimeout, this);
|
||||
|
||||
toxav_register_audio_callback(toxav, playCallAudio, this);
|
||||
toxav_register_video_callback(toxav, playCallVideo, this);
|
||||
|
||||
HistoryKeeper::getInstance()->importAvatarToDatabase(getSelfId().toString().left(64));
|
||||
QPixmap pic = Settings::getInstance().getSavedAvatar(getSelfId().toString());
|
||||
if (!pic.isNull() && !pic.size().isEmpty())
|
||||
|
@ -356,6 +330,7 @@ void Core::start()
|
|||
GUI::setEnabled(true);
|
||||
|
||||
process(); // starts its own timer
|
||||
av->start();
|
||||
}
|
||||
|
||||
/* Using the now commented out statements in checkConnection(), I watched how
|
||||
|
@ -370,11 +345,13 @@ void Core::start()
|
|||
void Core::process()
|
||||
{
|
||||
if (!isReady())
|
||||
{
|
||||
av->stop();
|
||||
return;
|
||||
}
|
||||
|
||||
static int tolerance = CORE_DISCONNECT_TOLERANCE;
|
||||
tox_iterate(tox);
|
||||
toxav_do(toxav);
|
||||
|
||||
#ifdef DEBUG
|
||||
//we want to see the debug messages immediately
|
||||
|
@ -391,8 +368,7 @@ void Core::process()
|
|||
tolerance = 3*CORE_DISCONNECT_TOLERANCE;
|
||||
}
|
||||
|
||||
unsigned sleeptime = qMin(tox_iteration_interval(tox), toxav_do_interval(toxav));
|
||||
sleeptime = qMin(sleeptime, CoreFile::corefileIterationInterval());
|
||||
unsigned sleeptime = qMin(tox_iteration_interval(tox), CoreFile::corefileIterationInterval());
|
||||
toxTimer->start(sleeptime);
|
||||
}
|
||||
|
||||
|
@ -753,9 +729,7 @@ void Core::removeGroup(int groupId, bool fake)
|
|||
return;
|
||||
|
||||
tox_del_groupchat(tox, groupId);
|
||||
|
||||
if (groupCalls[groupId].active)
|
||||
leaveGroupCall(groupId);
|
||||
av->leaveGroupCall(groupId);
|
||||
}
|
||||
|
||||
QString Core::getUsername() const
|
||||
|
@ -1078,11 +1052,6 @@ void Core::createGroup(uint8_t type)
|
|||
}
|
||||
}
|
||||
|
||||
bool Core::isGroupAvEnabled(int groupId)
|
||||
{
|
||||
return tox_group_get_type(tox, groupId) == TOX_GROUPCHAT_TYPE_AV;
|
||||
}
|
||||
|
||||
bool Core::isFriendOnline(uint32_t friendId) const
|
||||
{
|
||||
return tox_friend_get_connection_status(tox, friendId, nullptr) != TOX_CONNECTION_NONE;
|
||||
|
@ -1225,7 +1194,7 @@ QString Core::getPeerName(const ToxId& id) const
|
|||
|
||||
bool Core::isReady()
|
||||
{
|
||||
return toxav && tox && ready;
|
||||
return av && av->getToxAv() && tox && ready;
|
||||
}
|
||||
|
||||
void Core::setNospam(uint32_t nospam)
|
||||
|
@ -1235,31 +1204,10 @@ void Core::setNospam(uint32_t nospam)
|
|||
tox_self_set_nospam(tox, nospam);
|
||||
}
|
||||
|
||||
void Core::resetCallSources()
|
||||
{
|
||||
for (ToxGroupCall& call : groupCalls)
|
||||
{
|
||||
for (ALuint source : call.alSources)
|
||||
alDeleteSources(1, &source);
|
||||
call.alSources.clear();
|
||||
}
|
||||
|
||||
for (ToxCall& call : calls)
|
||||
{
|
||||
if (call.active && call.alSource)
|
||||
{
|
||||
ALuint tmp = call.alSource;
|
||||
call.alSource = 0;
|
||||
alDeleteSources(1, &tmp);
|
||||
|
||||
alGenSources(1, &call.alSource);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void Core::killTimers(bool onlyStop)
|
||||
{
|
||||
assert(QThread::currentThread() == coreThread);
|
||||
av->stop();
|
||||
toxTimer->stop();
|
||||
if (!onlyStop)
|
||||
{
|
||||
|
|
|
@ -29,7 +29,6 @@
|
|||
#include <tox/toxencryptsave.h>
|
||||
|
||||
#include "corestructs.h"
|
||||
#include "coreav.h"
|
||||
#include "coredefines.h"
|
||||
#include "toxid.h"
|
||||
|
||||
|
@ -38,11 +37,9 @@ template <typename T> class QList;
|
|||
class QTimer;
|
||||
class QString;
|
||||
class CString;
|
||||
class VideoSource;
|
||||
class VideoFrame;
|
||||
#ifdef QTOX_FILTER_AUDIO
|
||||
class AudioFilterer;
|
||||
#endif
|
||||
struct ToxAV;
|
||||
class CoreAV;
|
||||
struct vpx_image;
|
||||
|
||||
class Core : public QObject
|
||||
{
|
||||
|
@ -50,6 +47,7 @@ class Core : public QObject
|
|||
public:
|
||||
explicit Core(QThread* coreThread, Profile& profile);
|
||||
static Core* getInstance(); ///< Returns the global widget's Core instance
|
||||
CoreAV* getAv();
|
||||
~Core();
|
||||
|
||||
static const QString TOX_EXT;
|
||||
|
@ -88,13 +86,8 @@ public:
|
|||
static QByteArray decryptData(const QByteArray& data, const TOX_PASS_KEY &encryptionKey);
|
||||
static QByteArray decryptData(const QByteArray& data); ///< Uses the default profile's key
|
||||
|
||||
VideoSource* getVideoSourceFromCall(int callNumber); ///< Get a call's video source
|
||||
|
||||
static bool anyActiveCalls(); ///< true is any calls are currently active (note: a call about to start is not yet active)
|
||||
bool isReady(); ///< Most of the API shouldn't be used until Core is ready, call start() first
|
||||
|
||||
void resetCallSources(); ///< Forces to regenerate each call's audio sources
|
||||
|
||||
public slots:
|
||||
void start(); ///< Initializes the core, must be called before anything else
|
||||
void reset(); ///< Reinitialized the core. Must be called from the Core thread, with the GUI thread ready to process events.
|
||||
|
@ -132,27 +125,8 @@ public slots:
|
|||
void pauseResumeFileSend(uint32_t friendId, uint32_t fileNum);
|
||||
void pauseResumeFileRecv(uint32_t friendId, uint32_t fileNum);
|
||||
|
||||
void answerCall(int callId);
|
||||
void rejectCall(int callId);
|
||||
void hangupCall(int callId);
|
||||
void startCall(uint32_t friendId, bool video=false);
|
||||
void cancelCall(int callId, uint32_t friendId);
|
||||
|
||||
void micMuteToggle(int callId);
|
||||
void volMuteToggle(int callId);
|
||||
|
||||
void setNospam(uint32_t nospam);
|
||||
|
||||
bool isGroupAvEnabled(int groupId); ///< True for AV groups, false for text-only groups
|
||||
|
||||
static void joinGroupCall(int groupId); ///< Starts a call in an existing AV groupchat. Call from the GUI thread.
|
||||
static void leaveGroupCall(int groupId); ///< Will not leave the group, just stop the call. Call from the GUI thread.
|
||||
static void disableGroupCallMic(int groupId);
|
||||
static void disableGroupCallVol(int groupId);
|
||||
static void enableGroupCallMic(int groupId);
|
||||
static void enableGroupCallVol(int groupId);
|
||||
static bool isGroupCallMicEnabled(int groupId);
|
||||
static bool isGroupCallVolEnabled(int groupId);
|
||||
|
||||
signals:
|
||||
void connected();
|
||||
|
@ -218,21 +192,6 @@ signals:
|
|||
|
||||
void fileSendFailed(uint32_t friendId, const QString& fname);
|
||||
|
||||
void avInvite(uint32_t friendId, int callIndex, bool video);
|
||||
void avStart(uint32_t friendId, int callIndex, bool video);
|
||||
void avCancel(uint32_t friendId, int callIndex);
|
||||
void avEnd(uint32_t friendId, int callIndex);
|
||||
void avRinging(uint32_t friendId, int callIndex, bool video);
|
||||
void avStarting(uint32_t friendId, int callIndex, bool video);
|
||||
void avEnding(uint32_t friendId, int callIndex);
|
||||
void avRequestTimeout(uint32_t friendId, int callIndex);
|
||||
void avPeerTimeout(uint32_t friendId, int callIndex);
|
||||
void avMediaChange(uint32_t friendId, int callIndex, bool videoEnabled);
|
||||
void avCallFailed(uint32_t friendId);
|
||||
void avRejected(uint32_t friendId, int callIndex);
|
||||
|
||||
void videoFrameReceived(vpx_image* frame);
|
||||
|
||||
private:
|
||||
static void onFriendRequest(Tox* tox, const uint8_t* cUserId, const uint8_t* cMessage,
|
||||
size_t cMessageSize, void* core);
|
||||
|
@ -256,28 +215,6 @@ private:
|
|||
const uint8_t* title, uint8_t len, void* _core);
|
||||
static void onReadReceiptCallback(Tox *tox, uint32_t friendId, uint32_t receipt, void *core);
|
||||
|
||||
static void onAvInvite(void* toxav, int32_t call_index, void* core);
|
||||
static void onAvStart(void* toxav, int32_t call_index, void* core);
|
||||
static void onAvCancel(void* toxav, int32_t call_index, void* core);
|
||||
static void onAvReject(void* toxav, int32_t call_index, void* core);
|
||||
static void onAvEnd(void* toxav, int32_t call_index, void* core);
|
||||
static void onAvRinging(void* toxav, int32_t call_index, void* core);
|
||||
static void onAvRequestTimeout(void* toxav, int32_t call_index, void* core);
|
||||
static void onAvPeerTimeout(void* toxav, int32_t call_index, void* core);
|
||||
static void onAvMediaChange(void *toxav, int32_t call_index, void* core);
|
||||
|
||||
static void sendGroupCallAudio(int groupId, ToxAv* toxav);
|
||||
|
||||
static void prepareCall(uint32_t friendId, int callId, ToxAv *toxav, bool videoEnabled);
|
||||
static void cleanupCall(int callId);
|
||||
static void playCallAudio(void *toxav, int32_t callId, const int16_t *data,
|
||||
uint16_t samples, void *user_data); // Callback
|
||||
static void sendCallAudio(int callId, ToxAv* toxav);
|
||||
static void playAudioBuffer(ALuint alSource, const int16_t *data, int samples,
|
||||
unsigned channels, int sampleRate);
|
||||
static void playCallVideo(void *toxav, int32_t callId, const vpx_image_t* img, void *user_data);
|
||||
static void sendCallVideo(int callId, ToxAv* toxav, std::shared_ptr<VideoFrame> frame);
|
||||
|
||||
bool checkConnection();
|
||||
|
||||
void checkEncryptedHistory();
|
||||
|
@ -293,24 +230,17 @@ private slots:
|
|||
|
||||
private:
|
||||
Tox* tox;
|
||||
ToxAv* toxav;
|
||||
CoreAV* av;
|
||||
QTimer *toxTimer;
|
||||
Profile& profile;
|
||||
static ToxCall calls[TOXAV_MAX_CALLS];
|
||||
#ifdef QTOX_FILTER_AUDIO
|
||||
static AudioFilterer * filterer[TOXAV_MAX_CALLS];
|
||||
#endif
|
||||
static QHash<int, ToxGroupCall> groupCalls; // Maps group IDs to ToxGroupCalls
|
||||
QMutex messageSendMutex;
|
||||
bool ready;
|
||||
|
||||
static const int videobufsize;
|
||||
static uint8_t* videobuf;
|
||||
|
||||
static QThread *coreThread;
|
||||
|
||||
friend class Audio; ///< Audio can access our calls directly to reduce latency
|
||||
friend class CoreFile; ///< CoreFile can access tox* and emit our signals
|
||||
friend class CoreAV; ///< CoreAV accesses our toxav* for now
|
||||
};
|
||||
|
||||
#endif // CORE_HPP
|
||||
|
|
1043
src/core/coreav.cpp
1043
src/core/coreav.cpp
File diff suppressed because it is too large
Load Diff
|
@ -21,7 +21,9 @@
|
|||
#ifndef COREAV_H
|
||||
#define COREAV_H
|
||||
|
||||
#include <QHash>
|
||||
#include <QObject>
|
||||
#include <memory>
|
||||
#include <atomic>
|
||||
#include <tox/toxav.h>
|
||||
|
||||
#if defined(__APPLE__) && defined(__MACH__)
|
||||
|
@ -32,33 +34,108 @@
|
|||
#include <AL/alc.h>
|
||||
#endif
|
||||
|
||||
#include "src/core/toxcall.h"
|
||||
|
||||
#ifdef QTOX_FILTER_AUDIO
|
||||
class AudioFilterer;
|
||||
#endif
|
||||
|
||||
class QTimer;
|
||||
class QThread;
|
||||
class CoreVideoSource;
|
||||
class CameraSource;
|
||||
class VideoSource;
|
||||
class VideoFrame;
|
||||
class CoreAV;
|
||||
struct vpx_image;
|
||||
|
||||
struct ToxCall
|
||||
class CoreAV : public QObject
|
||||
{
|
||||
ToxAvCSettings codecSettings;
|
||||
QTimer *sendAudioTimer;
|
||||
int32_t callId;
|
||||
uint32_t friendId;
|
||||
bool videoEnabled;
|
||||
bool active;
|
||||
bool muteMic;
|
||||
bool muteVol;
|
||||
ALuint alSource;
|
||||
CoreVideoSource* videoSource;
|
||||
};
|
||||
Q_OBJECT
|
||||
|
||||
struct ToxGroupCall
|
||||
{
|
||||
ToxAvCSettings codecSettings;
|
||||
QTimer *sendAudioTimer;
|
||||
int groupId;
|
||||
bool active = false;
|
||||
bool muteMic;
|
||||
bool muteVol;
|
||||
QHash<int, ALuint> alSources;
|
||||
public:
|
||||
CoreAV(Tox* tox);
|
||||
~CoreAV();
|
||||
|
||||
const ToxAV* getToxAv() const;
|
||||
|
||||
bool anyActiveCalls(); ///< true is any calls are currently active (note: a call about to start is not yet active)
|
||||
bool isCallVideoEnabled(uint32_t friendNum);
|
||||
bool sendCallAudio(uint32_t friendNum); ///< Returns false only on error, but not if there's nothing to send
|
||||
void sendCallVideo(uint32_t friendNum, std::shared_ptr<VideoFrame> frame);
|
||||
bool sendGroupCallAudio(int groupNum);
|
||||
|
||||
VideoSource* getVideoSourceFromCall(int callNumber); ///< Get a call's video source
|
||||
void resetCallSources(); ///< Forces to regenerate each call's audio sources
|
||||
void sendNoVideo(); ///< Signal to all peers that we're not sending video anymore. The next frame sent cancels this.
|
||||
|
||||
void joinGroupCall(int groupNum); ///< Starts a call in an existing AV groupchat. Call from the GUI thread.
|
||||
void leaveGroupCall(int groupNum); ///< Will not leave the group, just stop the call. Call from the GUI thread.
|
||||
void disableGroupCallMic(int groupNum);
|
||||
void disableGroupCallVol(int groupNum);
|
||||
void enableGroupCallMic(int groupNum);
|
||||
void enableGroupCallVol(int groupNum);
|
||||
bool isGroupCallMicEnabled(int groupNum) const;
|
||||
bool isGroupCallVolEnabled(int groupNum) const;
|
||||
bool isGroupAvEnabled(int groupNum) const; ///< True for AV groups, false for text-only groups
|
||||
|
||||
void micMuteToggle(uint32_t friendNum);
|
||||
void volMuteToggle(uint32_t friendNum);
|
||||
|
||||
public slots:
|
||||
bool startCall(uint32_t friendNum, bool video=false);
|
||||
bool answerCall(uint32_t friendNum);
|
||||
bool cancelCall(uint32_t friendNum);
|
||||
void timeoutCall(uint32_t friendNum);
|
||||
/// Starts the CoreAV main loop that calls toxav's main loop
|
||||
void start();
|
||||
/// Stops the main loop
|
||||
void stop();
|
||||
|
||||
signals:
|
||||
void avInvite(uint32_t friendId, bool video); ///< Sent when a friend calls us
|
||||
void avStart(uint32_t friendId, bool video); ///< Sent when a call we initiated has started
|
||||
void avEnd(uint32_t friendId); ///< Sent when a call was ended by the peer
|
||||
|
||||
private slots:
|
||||
static void callCallback(ToxAV *toxAV, uint32_t friendNum, bool audio, bool video, void* self);
|
||||
static void stateCallback(ToxAV *, uint32_t friendNum, uint32_t state, void* self);
|
||||
static void bitrateCallback(ToxAV *toxAV, uint32_t friendNum, uint32_t arate, uint32_t vrate, void* self);
|
||||
void killTimerFromThread(); ///< Calls itself blocking queued on the coreav thread
|
||||
|
||||
private:
|
||||
void process();
|
||||
static void audioFrameCallback(ToxAV *toxAV, uint32_t friendNum, const int16_t *pcm, size_t sampleCount,
|
||||
uint8_t channels, uint32_t samplingRate, void* self);
|
||||
static void videoFrameCallback(ToxAV *toxAV, uint32_t friendNum, uint16_t w, uint16_t h,
|
||||
const uint8_t *y, const uint8_t *u, const uint8_t *v,
|
||||
int32_t ystride, int32_t ustride, int32_t vstride, void* self);
|
||||
|
||||
private:
|
||||
static constexpr uint32_t AUDIO_DEFAULT_BITRATE = 64; ///< In kb/s. More than enough for Opus.
|
||||
static constexpr uint32_t VIDEO_DEFAULT_BITRATE = 3000; ///< Picked at random by fair dice roll.
|
||||
|
||||
private:
|
||||
ToxAV* toxav;
|
||||
std::unique_ptr<QThread> coreavThread;
|
||||
std::unique_ptr<QTimer> iterateTimer;
|
||||
static IndexedList<ToxFriendCall> calls;
|
||||
static IndexedList<ToxGroupCall> groupCalls; // Maps group IDs to ToxGroupCalls
|
||||
/**
|
||||
* This flag is to be acquired before switching in a blocking way between the UI and CoreAV thread.
|
||||
* The CoreAV thread must have priority for the flag, other threads should back off or release it quickly.
|
||||
*
|
||||
* CoreAV needs to interface with three threads, the toxcore/Core thread that fires non-payload
|
||||
* toxav callbacks, the toxav/CoreAV thread that fires AV payload callbacks and manages
|
||||
* most of CoreAV's members, and the UI thread, which calls our [start/answer/cancel]Call functions
|
||||
* and which we call via signals.
|
||||
* When the UI calls us, we switch from the UI thread to the CoreAV thread to do the processing,
|
||||
* when toxcore fires a non-payload av callback, we do the processing in the CoreAV thread and then
|
||||
* switch to the UI thread to send it a signal. Both switches block both threads, so this would deadlock.
|
||||
*/
|
||||
std::atomic_flag threadSwitchLock;
|
||||
|
||||
friend class Audio;
|
||||
};
|
||||
|
||||
#endif // COREAV_H
|
||||
|
|
|
@ -21,7 +21,6 @@
|
|||
#ifndef COREDEFINES_H
|
||||
#define COREDEFINES_H
|
||||
|
||||
#define TOXAV_MAX_CALLS 16
|
||||
#define TOXAV_RINGING_TIME 45
|
||||
|
||||
// TODO: Put that in the settings
|
||||
|
|
45
src/core/indexedlist.h
Normal file
45
src/core/indexedlist.h
Normal file
|
@ -0,0 +1,45 @@
|
|||
#ifndef INDEXEDLIST_H
|
||||
#define INDEXEDLIST_H
|
||||
|
||||
#include <vector>
|
||||
#include <algorithm>
|
||||
|
||||
/// More or less a hashmap, indexed by the noexcept move-only value type's operator int
|
||||
/// Really nice to store our ToxCall objects.
|
||||
template <typename T>
|
||||
class IndexedList
|
||||
{
|
||||
public:
|
||||
explicit IndexedList() = default;
|
||||
|
||||
// Qt
|
||||
inline bool isEmpty() { return v.empty(); }
|
||||
bool contains(int i) { return std::find_if(begin(), end(), [i](T& t){return (int)t == i;}) != end(); }
|
||||
void remove(int i) { v.erase(std::remove_if(begin(), end(), [i](T& t){return (int)t == i;}), end()); }
|
||||
T& operator[](int i)
|
||||
{
|
||||
iterator it = std::find_if(begin(), end(), [i](T& t){return (int)t == i;});
|
||||
if (it == end())
|
||||
it = insert({});
|
||||
return *it;
|
||||
}
|
||||
|
||||
// STL
|
||||
using iterator = typename std::vector<T>::iterator;
|
||||
using const_iterator = typename std::vector<T>::const_iterator;
|
||||
|
||||
inline iterator begin() { return v.begin(); }
|
||||
inline const_iterator begin() const { return v.begin(); }
|
||||
inline const_iterator cbegin() const { return v.cbegin(); }
|
||||
inline iterator end() { return v.end(); }
|
||||
inline const_iterator end() const { return v.end(); }
|
||||
inline const_iterator cend() const { return v.cend(); }
|
||||
inline iterator erase(iterator pos) { return v.erase(pos); }
|
||||
inline iterator erase(iterator first, iterator last) { return v.erase(first, last); }
|
||||
inline iterator insert(T&& value) { v.push_back(std::move(value)); return --v.end(); }
|
||||
|
||||
private:
|
||||
std::vector<T> v;
|
||||
};
|
||||
|
||||
#endif // INDEXEDLIST_H
|
217
src/core/toxcall.cpp
Normal file
217
src/core/toxcall.cpp
Normal file
|
@ -0,0 +1,217 @@
|
|||
#include "src/audio/audio.h"
|
||||
#include "src/core/toxcall.h"
|
||||
#include "src/core/coreav.h"
|
||||
#include "src/persistence/settings.h"
|
||||
#include "src/video/camerasource.h"
|
||||
#include "src/video/corevideosource.h"
|
||||
#include <QTimer>
|
||||
#include <QtConcurrent/QtConcurrent>
|
||||
|
||||
#ifdef QTOX_FILTER_AUDIO
|
||||
#include "src/audio/audiofilterer.h"
|
||||
#endif
|
||||
|
||||
using namespace std;
|
||||
|
||||
ToxCall::ToxCall(uint32_t CallId)
|
||||
: sendAudioTimer{new QTimer}, callId{CallId},
|
||||
inactive{true}, muteMic{false}, muteVol{false}, alSource{0}
|
||||
{
|
||||
sendAudioTimer->setInterval(5);
|
||||
sendAudioTimer->setSingleShot(true);
|
||||
|
||||
Audio::getInstance().subscribeInput();
|
||||
|
||||
#ifdef QTOX_FILTER_AUDIO
|
||||
if (Settings::getInstance().getFilterAudio())
|
||||
{
|
||||
filterer = new AudioFilterer();
|
||||
filterer->startFilter(AUDIO_SAMPLE_RATE);
|
||||
}
|
||||
else
|
||||
{
|
||||
filterer = nullptr;
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
ToxCall::ToxCall(ToxCall&& other) noexcept
|
||||
: sendAudioTimer{other.sendAudioTimer}, callId{other.callId},
|
||||
inactive{other.inactive}, muteMic{other.muteMic}, muteVol{other.muteVol},
|
||||
alSource{other.alSource}
|
||||
{
|
||||
other.sendAudioTimer = nullptr;
|
||||
other.callId = numeric_limits<decltype(callId)>::max();
|
||||
other.alSource = 0;
|
||||
|
||||
#ifdef QTOX_FILTER_AUDIO
|
||||
filterer = other.filterer;
|
||||
other.filterer = nullptr;
|
||||
#endif
|
||||
}
|
||||
|
||||
ToxCall::~ToxCall()
|
||||
{
|
||||
if (sendAudioTimer)
|
||||
{
|
||||
QObject::disconnect(sendAudioTimer, nullptr, nullptr, nullptr);
|
||||
sendAudioTimer->stop();
|
||||
Audio::getInstance().unsubscribeInput();
|
||||
}
|
||||
|
||||
if (alSource)
|
||||
Audio::deleteSource(&alSource);
|
||||
|
||||
#ifdef QTOX_FILTER_AUDIO
|
||||
if (filterer)
|
||||
delete filterer;
|
||||
#endif
|
||||
}
|
||||
|
||||
const ToxCall& ToxCall::operator=(ToxCall&& other) noexcept
|
||||
{
|
||||
sendAudioTimer = other.sendAudioTimer;
|
||||
other.sendAudioTimer = nullptr;
|
||||
callId = other.callId;
|
||||
other.callId = numeric_limits<decltype(callId)>::max();
|
||||
inactive = other.inactive;
|
||||
muteMic = other.muteMic;
|
||||
muteVol = other.muteVol;
|
||||
alSource = other.alSource;
|
||||
other.alSource = 0;
|
||||
|
||||
#ifdef QTOX_FILTER_AUDIO
|
||||
filterer = other.filterer;
|
||||
other.filterer = nullptr;
|
||||
#endif
|
||||
|
||||
return *this;
|
||||
}
|
||||
|
||||
void ToxFriendCall::startTimeout()
|
||||
{
|
||||
if (!timeoutTimer)
|
||||
{
|
||||
timeoutTimer = new QTimer();
|
||||
// We might move, so we need copies of members. CoreAV won't move while we're alive
|
||||
CoreAV* avCopy = av;
|
||||
auto callIdCopy = callId;
|
||||
QObject::connect(timeoutTimer, &QTimer::timeout, [avCopy, callIdCopy](){
|
||||
avCopy->timeoutCall(callIdCopy);
|
||||
});
|
||||
}
|
||||
|
||||
if (!timeoutTimer->isActive())
|
||||
timeoutTimer->start(CALL_TIMEOUT);
|
||||
}
|
||||
|
||||
void ToxFriendCall::stopTimeout()
|
||||
{
|
||||
if (!timeoutTimer)
|
||||
return;
|
||||
|
||||
timeoutTimer->stop();
|
||||
delete timeoutTimer;
|
||||
timeoutTimer = nullptr;
|
||||
}
|
||||
|
||||
ToxFriendCall::ToxFriendCall(uint32_t FriendNum, bool VideoEnabled, CoreAV& av)
|
||||
: ToxCall(FriendNum),
|
||||
videoEnabled{VideoEnabled}, nullVideoBitrate{false}, videoSource{nullptr},
|
||||
state{static_cast<TOXAV_FRIEND_CALL_STATE>(0)},
|
||||
av{&av}, timeoutTimer{nullptr}
|
||||
{
|
||||
auto audioTimerCopy = sendAudioTimer; // this might change after a move, but not sendAudioTimer
|
||||
QObject::connect(sendAudioTimer, &QTimer::timeout, [FriendNum,&av,audioTimerCopy]()
|
||||
{
|
||||
// If sendCallAudio returns false, there was a serious error and we might as well stop the timer
|
||||
if (av.sendCallAudio(FriendNum))
|
||||
audioTimerCopy->start();
|
||||
});
|
||||
sendAudioTimer->start();
|
||||
|
||||
if (videoEnabled)
|
||||
{
|
||||
videoSource = new CoreVideoSource;
|
||||
CameraSource& source = CameraSource::getInstance();
|
||||
if (!source.isOpen())
|
||||
source.open();
|
||||
source.subscribe();
|
||||
QObject::connect(&source, &VideoSource::frameAvailable,
|
||||
[FriendNum,&av](shared_ptr<VideoFrame> frame){av.sendCallVideo(FriendNum,frame);});
|
||||
}
|
||||
}
|
||||
|
||||
ToxFriendCall::ToxFriendCall(ToxFriendCall&& other) noexcept
|
||||
: ToxCall(move(other)),
|
||||
videoEnabled{other.videoEnabled}, nullVideoBitrate{other.nullVideoBitrate},
|
||||
videoSource{other.videoSource}, state{other.state},
|
||||
av{other.av}, timeoutTimer{other.timeoutTimer}
|
||||
{
|
||||
other.videoEnabled = false;
|
||||
other.videoSource = nullptr;
|
||||
other.timeoutTimer = nullptr;
|
||||
}
|
||||
|
||||
ToxFriendCall::~ToxFriendCall()
|
||||
{
|
||||
if (timeoutTimer)
|
||||
delete timeoutTimer;
|
||||
|
||||
if (videoEnabled)
|
||||
{
|
||||
// This destructor could be running in a toxav callback while holding toxav locks.
|
||||
// If the CameraSource thread calls toxav *_send_frame, we might deadlock the toxav and CameraSource locks,
|
||||
// so we unsuscribe asynchronously, it's fine if the webcam takes a couple milliseconds more to poweroff.
|
||||
QtConcurrent::run([](){CameraSource::getInstance().unsubscribe();});
|
||||
if (videoSource)
|
||||
{
|
||||
videoSource->setDeleteOnClose(true);
|
||||
videoSource = nullptr;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const ToxFriendCall& ToxFriendCall::operator=(ToxFriendCall&& other) noexcept
|
||||
{
|
||||
ToxCall::operator =(move(other));
|
||||
videoEnabled = other.videoEnabled;
|
||||
other.videoEnabled = false;
|
||||
videoSource = other.videoSource;
|
||||
other.videoSource = nullptr;
|
||||
state = other.state;
|
||||
timeoutTimer = other.timeoutTimer;
|
||||
other.timeoutTimer = nullptr;
|
||||
av = other.av;
|
||||
nullVideoBitrate = other.nullVideoBitrate;
|
||||
|
||||
return *this;
|
||||
}
|
||||
|
||||
ToxGroupCall::ToxGroupCall(int GroupNum, CoreAV &av)
|
||||
: ToxCall(static_cast<decltype(callId)>(GroupNum))
|
||||
{
|
||||
static_assert(numeric_limits<decltype(callId)>::max() >= numeric_limits<decltype(GroupNum)>::max(),
|
||||
"The callId must be able to represent any group number, change its type if needed");
|
||||
|
||||
auto audioTimerCopy = sendAudioTimer; // this might change after a move, but not sendAudioTimer
|
||||
QObject::connect(sendAudioTimer, &QTimer::timeout, [GroupNum,&av,audioTimerCopy]()
|
||||
{
|
||||
// If sendGroupCallAudio returns false, there was a serious error and we might as well stop the timer
|
||||
if (av.sendGroupCallAudio(GroupNum))
|
||||
audioTimerCopy->start();
|
||||
});
|
||||
sendAudioTimer->start();
|
||||
}
|
||||
|
||||
ToxGroupCall::ToxGroupCall(ToxGroupCall&& other) noexcept
|
||||
: ToxCall(move(other))
|
||||
{
|
||||
}
|
||||
|
||||
const ToxGroupCall &ToxGroupCall::operator=(ToxGroupCall &&other) noexcept
|
||||
{
|
||||
ToxCall::operator =(move(other));
|
||||
|
||||
return *this;
|
||||
}
|
88
src/core/toxcall.h
Normal file
88
src/core/toxcall.h
Normal file
|
@ -0,0 +1,88 @@
|
|||
#ifndef TOXCALL_H
|
||||
#define TOXCALL_H
|
||||
|
||||
#include <cstdint>
|
||||
#include "src/core/indexedlist.h"
|
||||
|
||||
#if defined(__APPLE__) && defined(__MACH__)
|
||||
#include <OpenAL/al.h>
|
||||
#include <OpenAL/alc.h>
|
||||
#else
|
||||
#include <AL/al.h>
|
||||
#include <AL/alc.h>
|
||||
#endif
|
||||
|
||||
#include <tox/toxav.h>
|
||||
|
||||
class QTimer;
|
||||
class AudioFilterer;
|
||||
class CoreVideoSource;
|
||||
class CoreAV;
|
||||
|
||||
struct ToxCall
|
||||
{
|
||||
protected:
|
||||
ToxCall() = default;
|
||||
ToxCall(uint32_t CallId);
|
||||
~ToxCall();
|
||||
public:
|
||||
ToxCall(const ToxCall& other) = delete;
|
||||
ToxCall(ToxCall&& other) noexcept;
|
||||
|
||||
inline operator int() {return callId;}
|
||||
const ToxCall& operator=(const ToxCall& other) = delete;
|
||||
const ToxCall& operator=(ToxCall&& other) noexcept;
|
||||
|
||||
protected:
|
||||
QTimer* sendAudioTimer;
|
||||
|
||||
public:
|
||||
uint32_t callId; ///< Could be a friendNum or groupNum, must uniquely identify the call
|
||||
bool inactive; ///< True while we're not participating. (stopped group call, ringing but hasn't started yet, ...)
|
||||
bool muteMic;
|
||||
bool muteVol;
|
||||
ALuint alSource;
|
||||
|
||||
#ifdef QTOX_FILTER_AUDIO
|
||||
AudioFilterer* filterer;
|
||||
#endif
|
||||
};
|
||||
|
||||
struct ToxFriendCall : public ToxCall
|
||||
{
|
||||
ToxFriendCall() = default;
|
||||
ToxFriendCall(uint32_t FriendNum, bool VideoEnabled, CoreAV& av);
|
||||
ToxFriendCall(ToxFriendCall&& other) noexcept;
|
||||
~ToxFriendCall();
|
||||
|
||||
const ToxFriendCall& operator=(ToxFriendCall&& other) noexcept;
|
||||
|
||||
bool videoEnabled; ///< True if our user asked for a video call, sending and recving
|
||||
bool nullVideoBitrate; ///< True if our video bitrate is zero, i.e. if the device is closed
|
||||
CoreVideoSource* videoSource;
|
||||
TOXAV_FRIEND_CALL_STATE state; ///< State of the peer (not ours!)
|
||||
|
||||
void startTimeout();
|
||||
void stopTimeout();
|
||||
|
||||
protected:
|
||||
CoreAV* av;
|
||||
QTimer* timeoutTimer;
|
||||
|
||||
private:
|
||||
static constexpr int CALL_TIMEOUT = 45000;
|
||||
};
|
||||
|
||||
struct ToxGroupCall : public ToxCall
|
||||
{
|
||||
ToxGroupCall() = default;
|
||||
ToxGroupCall(int GroupNum, CoreAV& av);
|
||||
ToxGroupCall(ToxGroupCall&& other) noexcept;
|
||||
|
||||
const ToxGroupCall& operator=(ToxGroupCall&& other) noexcept;
|
||||
|
||||
// If you add something here, don't forget to override the ctors and move operators!
|
||||
};
|
||||
|
||||
#endif // TOXCALL_H
|
||||
|
25
src/main.cpp
25
src/main.cpp
|
@ -43,7 +43,8 @@
|
|||
#endif
|
||||
|
||||
#ifdef LOG_TO_FILE
|
||||
static QTextStream* logFile {nullptr};
|
||||
static std::unique_ptr<QTextStream> logFileStream {nullptr};
|
||||
static std::unique_ptr<QFile> logFileFile {nullptr};
|
||||
static QMutex mutex;
|
||||
#endif
|
||||
|
||||
|
@ -80,12 +81,12 @@ void logMessageHandler(QtMsgType type, const QMessageLogContext& ctxt, const QSt
|
|||
out << LogMsg;
|
||||
|
||||
#ifdef LOG_TO_FILE
|
||||
if (!logFile)
|
||||
if (!logFileStream)
|
||||
return;
|
||||
|
||||
QMutexLocker locker(&mutex);
|
||||
*logFile << LogMsg;
|
||||
logFile->flush();
|
||||
*logFileStream << LogMsg;
|
||||
logFileStream->flush();
|
||||
#endif
|
||||
}
|
||||
|
||||
|
@ -121,18 +122,17 @@ int main(int argc, char *argv[])
|
|||
sodium_init(); // For the auto-updater
|
||||
|
||||
#ifdef LOG_TO_FILE
|
||||
logFile = new QTextStream;
|
||||
QFile logfile(Settings::getInstance().getSettingsDirPath()+"qtox.log");
|
||||
if (logfile.open(QIODevice::Append))
|
||||
logFileStream.reset(new QTextStream);
|
||||
logFileFile.reset(new QFile(Settings::getInstance().getSettingsDirPath()+"qtox.log"));
|
||||
if (logFileFile->open(QIODevice::Append))
|
||||
{
|
||||
logFile->setDevice(&logfile);
|
||||
*logFile << QDateTime::currentDateTime().toString("\nyyyy-MM-dd HH:mm:ss' file logger starting\n'");
|
||||
logFileStream->setDevice(logFileFile.get());
|
||||
*logFileStream << QDateTime::currentDateTime().toString("\nyyyy-MM-dd HH:mm:ss' file logger starting\n'");
|
||||
}
|
||||
else
|
||||
{
|
||||
qWarning() << "Couldn't open log file!\n";
|
||||
delete logFile;
|
||||
logFile = nullptr;
|
||||
logFileStream.release();
|
||||
}
|
||||
#endif
|
||||
|
||||
|
@ -260,8 +260,7 @@ int main(int argc, char *argv[])
|
|||
int errorcode = a.exec();
|
||||
|
||||
#ifdef LOG_TO_FILE
|
||||
delete logFile;
|
||||
logFile = nullptr;
|
||||
logFileStream.release();
|
||||
#endif
|
||||
|
||||
Nexus::destroyInstance();
|
||||
|
|
|
@ -21,6 +21,7 @@
|
|||
#include "nexus.h"
|
||||
#include "src/persistence/profile.h"
|
||||
#include "src/core/core.h"
|
||||
#include "src/core/coreav.h"
|
||||
#include "persistence/settings.h"
|
||||
#include "video/camerasource.h"
|
||||
#include "widget/gui.h"
|
||||
|
@ -32,6 +33,7 @@
|
|||
#include <QFile>
|
||||
#include <QApplication>
|
||||
#include <cassert>
|
||||
#include <vpx/vpx_image.h>
|
||||
|
||||
#ifdef Q_OS_ANDROID
|
||||
#include <src/widget/androidgui.h>
|
||||
|
@ -47,6 +49,8 @@
|
|||
#include <QSignalMapper>
|
||||
#endif
|
||||
|
||||
Q_DECLARE_OPAQUE_POINTER(ToxAV*)
|
||||
|
||||
static Nexus* nexus{nullptr};
|
||||
|
||||
Nexus::Nexus(QObject *parent) :
|
||||
|
@ -88,6 +92,7 @@ void Nexus::start()
|
|||
qRegisterMetaType<int64_t>("int64_t");
|
||||
qRegisterMetaType<QPixmap>("QPixmap");
|
||||
qRegisterMetaType<Profile*>("Profile*");
|
||||
qRegisterMetaType<ToxAV*>("ToxAV*");
|
||||
qRegisterMetaType<ToxFile>("ToxFile");
|
||||
qRegisterMetaType<ToxFile::FileDirection>("ToxFile::FileDirection");
|
||||
qRegisterMetaType<std::shared_ptr<VideoFrame>>("std::shared_ptr<VideoFrame>");
|
||||
|
@ -226,7 +231,6 @@ void Nexus::showMainGUI()
|
|||
connect(core, &Core::groupTitleChanged, widget, &Widget::onGroupTitleChanged);
|
||||
connect(core, &Core::groupPeerAudioPlaying, widget, &Widget::onGroupPeerAudioPlaying);
|
||||
connect(core, &Core::emptyGroupCreated, widget, &Widget::onEmptyGroupCreated);
|
||||
connect(core, &Core::avInvite, widget, &Widget::playRingtone);
|
||||
connect(core, &Core::friendTypingChanged, widget, &Widget::onFriendTypingChanged);
|
||||
|
||||
connect(core, &Core::messageSentResult, widget, &Widget::onMessageSendResult);
|
||||
|
|
|
@ -118,8 +118,9 @@ CameraDevice* CameraDevice::open(QString devName, VideoMode mode)
|
|||
{
|
||||
screen = QApplication::desktop()->screenGeometry().size();
|
||||
// Workaround https://trac.ffmpeg.org/ticket/4574 by choping 1 px bottom and right
|
||||
screen.setWidth(screen.width()-1);
|
||||
screen.setHeight(screen.height()-1);
|
||||
// Actually, let's chop two pixels, toxav hates odd resolutions (off by one stride)
|
||||
screen.setWidth(screen.width()-2);
|
||||
screen.setHeight(screen.height()-2);
|
||||
}
|
||||
av_dict_set(&options, "video_size", QString("%1x%2").arg(screen.width()).arg(screen.height()).toStdString().c_str(), 0);
|
||||
if (mode.FPS)
|
||||
|
|
|
@ -37,8 +37,7 @@ CameraSource* CameraSource::instance{nullptr};
|
|||
CameraSource::CameraSource()
|
||||
: deviceName{"none"}, device{nullptr}, mode(VideoMode{0,0,0}),
|
||||
cctx{nullptr}, cctxOrig{nullptr}, videoStreamIndex{-1},
|
||||
biglock{false}, freelistLock{false},
|
||||
isOpen{false}, subscriptions{0}
|
||||
_isOpen{false}, streamBlocker{false}, subscriptions{0}
|
||||
{
|
||||
subscriptions = 0;
|
||||
av_register_all();
|
||||
|
@ -73,15 +72,12 @@ void CameraSource::open(const QString deviceName)
|
|||
|
||||
void CameraSource::open(const QString DeviceName, VideoMode Mode)
|
||||
{
|
||||
{
|
||||
bool expected = false;
|
||||
while (!biglock.compare_exchange_weak(expected, true))
|
||||
expected = false;
|
||||
}
|
||||
streamBlocker = true;
|
||||
QMutexLocker l{&biglock};
|
||||
|
||||
if (DeviceName == deviceName && Mode == mode)
|
||||
{
|
||||
biglock = false;
|
||||
streamBlocker = false;
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -90,12 +86,12 @@ void CameraSource::open(const QString DeviceName, VideoMode Mode)
|
|||
|
||||
deviceName = DeviceName;
|
||||
mode = Mode;
|
||||
isOpen = (deviceName != "none");
|
||||
_isOpen = (deviceName != "none");
|
||||
|
||||
if (subscriptions && isOpen)
|
||||
if (subscriptions && _isOpen)
|
||||
openDevice();
|
||||
|
||||
biglock = false;
|
||||
streamBlocker = false;
|
||||
}
|
||||
|
||||
void CameraSource::close()
|
||||
|
@ -103,20 +99,17 @@ void CameraSource::close()
|
|||
open("none");
|
||||
}
|
||||
|
||||
bool CameraSource::isOpen()
|
||||
{
|
||||
return _isOpen;
|
||||
}
|
||||
|
||||
CameraSource::~CameraSource()
|
||||
{
|
||||
// Fast lock, in case our stream thread is running
|
||||
{
|
||||
bool expected = false;
|
||||
while (!biglock.compare_exchange_weak(expected, true))
|
||||
expected = false;
|
||||
}
|
||||
QMutexLocker l{&biglock};
|
||||
|
||||
if (!isOpen)
|
||||
{
|
||||
biglock = false;
|
||||
if (!_isOpen)
|
||||
return;
|
||||
}
|
||||
|
||||
// Free all remaining VideoFrame
|
||||
// Locking must be done precisely this way to avoid races
|
||||
|
@ -133,13 +126,16 @@ CameraSource::~CameraSource()
|
|||
if (cctxOrig)
|
||||
avcodec_close(cctxOrig);
|
||||
|
||||
for(int i = 0; i < subscriptions; i++)
|
||||
device->close();
|
||||
if (device)
|
||||
{
|
||||
for(int i = 0; i < subscriptions; i++)
|
||||
device->close();
|
||||
device = nullptr;
|
||||
}
|
||||
|
||||
device = nullptr;
|
||||
// Memfence so the stream thread sees a nullptr device
|
||||
std::atomic_thread_fence(std::memory_order_release);
|
||||
biglock=false;
|
||||
l.unlock();
|
||||
|
||||
// Synchronize with our stream thread
|
||||
while (streamFuture.isRunning())
|
||||
|
@ -148,24 +144,17 @@ CameraSource::~CameraSource()
|
|||
|
||||
bool CameraSource::subscribe()
|
||||
{
|
||||
// Fast lock
|
||||
{
|
||||
bool expected = false;
|
||||
while (!biglock.compare_exchange_weak(expected, true))
|
||||
expected = false;
|
||||
}
|
||||
QMutexLocker l{&biglock};
|
||||
|
||||
if (!isOpen)
|
||||
if (!_isOpen)
|
||||
{
|
||||
++subscriptions;
|
||||
biglock = false;
|
||||
return true;
|
||||
}
|
||||
|
||||
if (openDevice())
|
||||
{
|
||||
++subscriptions;
|
||||
biglock = false;
|
||||
return true;
|
||||
}
|
||||
else
|
||||
|
@ -176,7 +165,6 @@ bool CameraSource::subscribe()
|
|||
videoStreamIndex = -1;
|
||||
// Memfence so the stream thread sees a nullptr device
|
||||
std::atomic_thread_fence(std::memory_order_release);
|
||||
biglock = false;
|
||||
return false;
|
||||
}
|
||||
|
||||
|
@ -184,31 +172,24 @@ bool CameraSource::subscribe()
|
|||
|
||||
void CameraSource::unsubscribe()
|
||||
{
|
||||
// Fast lock
|
||||
{
|
||||
bool expected = false;
|
||||
while (!biglock.compare_exchange_weak(expected, true))
|
||||
expected = false;
|
||||
}
|
||||
QMutexLocker l{&biglock};
|
||||
|
||||
if (!isOpen)
|
||||
if (!_isOpen)
|
||||
{
|
||||
subscriptions--;
|
||||
biglock = false;
|
||||
--subscriptions;
|
||||
return;
|
||||
}
|
||||
|
||||
if (!device)
|
||||
{
|
||||
qWarning() << "Unsubscribing with zero subscriber";
|
||||
biglock = false;
|
||||
return;
|
||||
}
|
||||
|
||||
if (subscriptions - 1 == 0)
|
||||
{
|
||||
closeDevice();
|
||||
biglock = false;
|
||||
l.unlock();
|
||||
|
||||
// Synchronize with our stream thread
|
||||
while (streamFuture.isRunning())
|
||||
|
@ -217,7 +198,6 @@ void CameraSource::unsubscribe()
|
|||
else
|
||||
{
|
||||
device->close();
|
||||
biglock = false;
|
||||
}
|
||||
subscriptions--;
|
||||
|
||||
|
@ -225,6 +205,8 @@ void CameraSource::unsubscribe()
|
|||
|
||||
bool CameraSource::openDevice()
|
||||
{
|
||||
qDebug() << "Opening device "<<deviceName;
|
||||
|
||||
if (device)
|
||||
{
|
||||
device->open();
|
||||
|
@ -296,6 +278,8 @@ bool CameraSource::openDevice()
|
|||
|
||||
void CameraSource::closeDevice()
|
||||
{
|
||||
qDebug() << "Closing device "<<deviceName;
|
||||
|
||||
// Free all remaining VideoFrame
|
||||
// Locking must be done precisely this way to avoid races
|
||||
for (int i = 0; i < freelist.size(); i++)
|
||||
|
@ -341,18 +325,13 @@ void CameraSource::stream()
|
|||
if (!frameFinished)
|
||||
return;
|
||||
|
||||
// Broadcast a new VideoFrame, it takes ownership of the AVFrame
|
||||
{
|
||||
bool expected = false;
|
||||
while (!freelistLock.compare_exchange_weak(expected, true))
|
||||
expected = false;
|
||||
}
|
||||
freelistLock.lock();
|
||||
|
||||
int freeFreelistSlot = getFreelistSlotLockless();
|
||||
auto frameFreeCb = std::bind(&CameraSource::freelistCallback, this, freeFreelistSlot);
|
||||
std::shared_ptr<VideoFrame> vframe = std::make_shared<VideoFrame>(frame, frameFreeCb);
|
||||
freelist.append(vframe);
|
||||
freelistLock = false;
|
||||
freelistLock.unlock();
|
||||
emit frameAvailable(vframe);
|
||||
}
|
||||
|
||||
|
@ -361,39 +340,30 @@ void CameraSource::stream()
|
|||
};
|
||||
|
||||
forever {
|
||||
// Fast lock
|
||||
{
|
||||
bool expected = false;
|
||||
while (!biglock.compare_exchange_weak(expected, true))
|
||||
expected = false;
|
||||
}
|
||||
biglock.lock();
|
||||
|
||||
// When a thread makes device null, it releases it, so we acquire here
|
||||
std::atomic_thread_fence(std::memory_order_acquire);
|
||||
if (!device)
|
||||
{
|
||||
biglock = false;
|
||||
biglock.unlock();
|
||||
return;
|
||||
}
|
||||
|
||||
streamLoop();
|
||||
|
||||
// Give a chance to other functions to pick up the lock if needed
|
||||
biglock = false;
|
||||
biglock.unlock();
|
||||
while (streamBlocker)
|
||||
QThread::yieldCurrentThread();
|
||||
QThread::yieldCurrentThread();
|
||||
}
|
||||
}
|
||||
|
||||
void CameraSource::freelistCallback(int freelistIndex)
|
||||
{
|
||||
// Fast spinlock
|
||||
{
|
||||
bool expected = false;
|
||||
while (!freelistLock.compare_exchange_weak(expected, true))
|
||||
expected = false;
|
||||
}
|
||||
QMutexLocker l{&freelistLock};
|
||||
freelist[freelistIndex].reset();
|
||||
freelistLock = false;
|
||||
}
|
||||
|
||||
int CameraSource::getFreelistSlotLockless()
|
||||
|
|
|
@ -55,6 +55,7 @@ public:
|
|||
void open(const QString deviceName);
|
||||
void open(const QString deviceName, VideoMode mode);
|
||||
void close(); ///< Equivalent to opening the source with the video device "none". Stops streaming.
|
||||
bool isOpen();
|
||||
|
||||
// VideoSource interface
|
||||
virtual bool subscribe() override;
|
||||
|
@ -90,8 +91,9 @@ private:
|
|||
VideoMode mode; ///< What mode we tried to open the device in, all zeros means default mode
|
||||
AVCodecContext* cctx, *cctxOrig; ///< Codec context of the camera's selected video stream
|
||||
int videoStreamIndex; ///< A camera can have multiple streams, this is the one we're decoding
|
||||
std::atomic_bool biglock, freelistLock; ///< True when locked. Faster than mutexes for video decoding.
|
||||
std::atomic_bool isOpen;
|
||||
QMutex biglock, freelistLock; ///< True when locked. Faster than mutexes for video decoding.
|
||||
std::atomic_bool _isOpen;
|
||||
std::atomic_bool streamBlocker; ///< Holds the streaming thread still when true
|
||||
std::atomic_int subscriptions; ///< Remember how many times we subscribed for RAII
|
||||
|
||||
static CameraSource* instance;
|
||||
|
|
|
@ -26,18 +26,16 @@ extern "C" {
|
|||
|
||||
CoreVideoSource::CoreVideoSource()
|
||||
: subscribers{0}, deleteOnClose{false},
|
||||
biglock{false}
|
||||
stopped{false}
|
||||
{
|
||||
}
|
||||
|
||||
void CoreVideoSource::pushFrame(const vpx_image_t* vpxframe)
|
||||
{
|
||||
// Fast lock
|
||||
{
|
||||
bool expected = false;
|
||||
while (!biglock.compare_exchange_weak(expected, true))
|
||||
expected = false;
|
||||
}
|
||||
if (stopped)
|
||||
return;
|
||||
|
||||
QMutexLocker locker(&biglock);
|
||||
|
||||
std::shared_ptr<VideoFrame> vframe;
|
||||
AVFrame* avframe;
|
||||
|
@ -46,11 +44,11 @@ void CoreVideoSource::pushFrame(const vpx_image_t* vpxframe)
|
|||
int dstStride, srcStride, minStride;
|
||||
|
||||
if (subscribers <= 0)
|
||||
goto end;
|
||||
return;
|
||||
|
||||
avframe = av_frame_alloc();
|
||||
if (!avframe)
|
||||
goto end;
|
||||
return;
|
||||
avframe->width = width;
|
||||
avframe->height = height;
|
||||
avframe->format = AV_PIX_FMT_YUV420P;
|
||||
|
@ -59,7 +57,7 @@ void CoreVideoSource::pushFrame(const vpx_image_t* vpxframe)
|
|||
if (!buf)
|
||||
{
|
||||
av_frame_free(&avframe);
|
||||
goto end;
|
||||
return;
|
||||
}
|
||||
avframe->opaque = buf;
|
||||
|
||||
|
@ -77,52 +75,46 @@ void CoreVideoSource::pushFrame(const vpx_image_t* vpxframe)
|
|||
|
||||
vframe = std::make_shared<VideoFrame>(avframe);
|
||||
emit frameAvailable(vframe);
|
||||
|
||||
end:
|
||||
biglock = false;
|
||||
}
|
||||
|
||||
bool CoreVideoSource::subscribe()
|
||||
{
|
||||
// Fast lock
|
||||
{
|
||||
bool expected = false;
|
||||
while (!biglock.compare_exchange_weak(expected, true))
|
||||
expected = false;
|
||||
}
|
||||
QMutexLocker locker(&biglock);
|
||||
++subscribers;
|
||||
biglock = false;
|
||||
return true;
|
||||
}
|
||||
|
||||
void CoreVideoSource::unsubscribe()
|
||||
{
|
||||
// Fast lock
|
||||
{
|
||||
bool expected = false;
|
||||
while (!biglock.compare_exchange_weak(expected, true))
|
||||
expected = false;
|
||||
}
|
||||
biglock.lock();
|
||||
if (--subscribers == 0)
|
||||
{
|
||||
if (deleteOnClose)
|
||||
{
|
||||
biglock = false;
|
||||
biglock.unlock();
|
||||
// DANGEROUS: No member access after this point, that's why we manually unlock
|
||||
delete this;
|
||||
return;
|
||||
}
|
||||
}
|
||||
biglock = false;
|
||||
biglock.unlock();
|
||||
}
|
||||
|
||||
void CoreVideoSource::setDeleteOnClose(bool newstate)
|
||||
{
|
||||
// Fast lock
|
||||
{
|
||||
bool expected = false;
|
||||
while (!biglock.compare_exchange_weak(expected, true))
|
||||
expected = false;
|
||||
}
|
||||
QMutexLocker locker(&biglock);
|
||||
deleteOnClose = newstate;
|
||||
biglock = false;
|
||||
}
|
||||
|
||||
void CoreVideoSource::stopSource()
|
||||
{
|
||||
QMutexLocker locker(&biglock);
|
||||
stopped = true;
|
||||
emit sourceStopped();
|
||||
}
|
||||
|
||||
void CoreVideoSource::restartSource()
|
||||
{
|
||||
QMutexLocker locker(&biglock);
|
||||
stopped = false;
|
||||
}
|
||||
|
|
|
@ -24,6 +24,7 @@
|
|||
#include <vpx/vpx_image.h>
|
||||
#include <atomic>
|
||||
#include "videosource.h"
|
||||
#include <QMutex>
|
||||
|
||||
/// A VideoSource that emits frames received by Core
|
||||
class CoreVideoSource : public VideoSource
|
||||
|
@ -35,8 +36,8 @@ public:
|
|||
virtual void unsubscribe() override;
|
||||
|
||||
private:
|
||||
// Only Core should create a CoreVideoSource since
|
||||
// only Core can push images to it
|
||||
// Only CoreAV should create a CoreVideoSource since
|
||||
// only CoreAV can push images to it
|
||||
CoreVideoSource();
|
||||
|
||||
/// Makes a copy of the vpx_image_t and emits it as a new VideoFrame
|
||||
|
@ -44,12 +45,19 @@ private:
|
|||
/// If true, self-delete after the last suscriber is gone
|
||||
void setDeleteOnClose(bool newstate);
|
||||
|
||||
/// Stopping the source will block any pushFrame calls from doing anything
|
||||
/// See the callers in CoreAV for the rationale
|
||||
void stopSource();
|
||||
void restartSource();
|
||||
|
||||
private:
|
||||
std::atomic_int subscribers; ///< Number of suscribers
|
||||
std::atomic_bool deleteOnClose; ///< If true, self-delete after the last suscriber is gone
|
||||
std::atomic_bool biglock; ///< Fast lock
|
||||
QMutex biglock;
|
||||
std::atomic_bool stopped;
|
||||
|
||||
friend class Core;
|
||||
friend class CoreAV;
|
||||
friend struct ToxFriendCall;
|
||||
};
|
||||
|
||||
#endif // COREVIDEOSOURCE_H
|
||||
|
|
|
@ -86,7 +86,6 @@ public:
|
|||
protected:
|
||||
void resizeEvent(QResizeEvent* event) final override
|
||||
{
|
||||
qDebug() << "Resize!";
|
||||
updateSize();
|
||||
QWidget::resizeEvent(event);
|
||||
}
|
||||
|
@ -94,13 +93,11 @@ protected:
|
|||
private slots:
|
||||
void updateSize()
|
||||
{
|
||||
qDebug() << videoSurface->isExpanding();
|
||||
if (videoSurface->isExpanding())
|
||||
{
|
||||
int width = videoSurface->height() * videoSurface->getRatio();
|
||||
videoSurface->setMinimumWidth(width);
|
||||
videoSurface->setMaximumWidth(width);
|
||||
qDebug() << videoSurface->minimumWidth();
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -33,6 +33,7 @@ class VideoSource : public QObject
|
|||
Q_OBJECT
|
||||
|
||||
public:
|
||||
virtual ~VideoSource() = default;
|
||||
/// If subscribe sucessfully opens the source, it will start emitting frameAvailable signals
|
||||
virtual bool subscribe() = 0;
|
||||
/// Stop emitting frameAvailable signals, and free associated resources if necessary
|
||||
|
@ -40,6 +41,9 @@ public:
|
|||
|
||||
signals:
|
||||
void frameAvailable(std::shared_ptr<VideoFrame> frame);
|
||||
/// Emitted when the source is stopped for an indefinite amount of time,
|
||||
/// but might restart sending frames again later
|
||||
void sourceStopped();
|
||||
};
|
||||
|
||||
#endif // VIDEOSOURCE_H
|
||||
|
|
|
@ -102,6 +102,7 @@ void VideoSurface::subscribe()
|
|||
{
|
||||
source->subscribe();
|
||||
connect(source, &VideoSource::frameAvailable, this, &VideoSurface::onNewFrameAvailable);
|
||||
connect(source, &VideoSource::sourceStopped, this, &VideoSurface::onSourceStopped);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -124,6 +125,7 @@ void VideoSurface::unsubscribe()
|
|||
|
||||
source->unsubscribe();
|
||||
disconnect(source, &VideoSource::frameAvailable, this, &VideoSurface::onNewFrameAvailable);
|
||||
disconnect(source, &VideoSource::sourceStopped, this, &VideoSurface::onSourceStopped);
|
||||
}
|
||||
|
||||
void VideoSurface::onNewFrameAvailable(std::shared_ptr<VideoFrame> newFrame)
|
||||
|
@ -148,6 +150,13 @@ void VideoSurface::onNewFrameAvailable(std::shared_ptr<VideoFrame> newFrame)
|
|||
update();
|
||||
}
|
||||
|
||||
void VideoSurface::onSourceStopped()
|
||||
{
|
||||
// If the source's stream is on hold, just revert back to the avatar view
|
||||
lastFrame.reset();
|
||||
update();
|
||||
}
|
||||
|
||||
void VideoSurface::paintEvent(QPaintEvent*)
|
||||
{
|
||||
lock();
|
||||
|
@ -157,6 +166,8 @@ void VideoSurface::paintEvent(QPaintEvent*)
|
|||
if (lastFrame)
|
||||
{
|
||||
QImage frame = lastFrame->toQImage(rect().size());
|
||||
if (frame.isNull())
|
||||
lastFrame.reset();
|
||||
painter.drawImage(boundingRect, frame, frame.rect(), Qt::NoFormatConversion);
|
||||
}
|
||||
else
|
||||
|
|
|
@ -55,6 +55,7 @@ protected:
|
|||
|
||||
private slots:
|
||||
void onNewFrameAvailable(std::shared_ptr<VideoFrame> newFrame);
|
||||
void onSourceStopped();
|
||||
|
||||
private:
|
||||
void recalulateBounds();
|
||||
|
|
|
@ -34,7 +34,9 @@
|
|||
#include <QSplitter>
|
||||
#include <cassert>
|
||||
#include "chatform.h"
|
||||
#include "src/audio/audio.h"
|
||||
#include "src/core/core.h"
|
||||
#include "src/core/coreav.h"
|
||||
#include "src/friend.h"
|
||||
#include "src/persistence/historykeeper.h"
|
||||
#include "src/widget/style.h"
|
||||
|
@ -62,8 +64,11 @@
|
|||
|
||||
ChatForm::ChatForm(Friend* chatFriend)
|
||||
: f(chatFriend)
|
||||
, callId{0}, isTyping{false}
|
||||
, isTyping{false}
|
||||
{
|
||||
Core* core = Core::getInstance();
|
||||
coreav = core->getAv();
|
||||
|
||||
nameLabel->setText(f->getDisplayedName());
|
||||
|
||||
avatar->setPixmap(QPixmap(":/img/contact_dark.svg"), Qt::transparent);
|
||||
|
@ -95,7 +100,7 @@ ChatForm::ChatForm(Friend* chatFriend)
|
|||
|
||||
loadHistoryAction = menu.addAction(QString(), this, SLOT(onLoadHistory()));
|
||||
|
||||
connect(Core::getInstance(), &Core::fileSendStarted, this, &ChatForm::startFileSend);
|
||||
connect(core, &Core::fileSendStarted, this, &ChatForm::startFileSend);
|
||||
connect(sendButton, &QPushButton::clicked, this, &ChatForm::onSendTriggered);
|
||||
connect(fileButton, &QPushButton::clicked, this, &ChatForm::onAttachClicked);
|
||||
connect(screenshotButton, &QPushButton::clicked, this, &ChatForm::onScreenshotClicked);
|
||||
|
@ -103,7 +108,7 @@ ChatForm::ChatForm(Friend* chatFriend)
|
|||
connect(videoButton, &QPushButton::clicked, this, &ChatForm::onVideoCallTriggered);
|
||||
connect(msgEdit, &ChatTextEdit::enterPressed, this, &ChatForm::onSendTriggered);
|
||||
connect(msgEdit, &ChatTextEdit::textChanged, this, &ChatForm::onTextEditChanged);
|
||||
connect(Core::getInstance(), &Core::fileSendFailed, this, &ChatForm::onFileSendFailed);
|
||||
connect(core, &Core::fileSendFailed, this, &ChatForm::onFileSendFailed);
|
||||
connect(this, &ChatForm::chatAreaCleared, getOfflineMsgEngine(), &OfflineMsgEngine::removeAllReciepts);
|
||||
connect(&typingTimer, &QTimer::timeout, this, [=]{
|
||||
Core::getInstance()->sendTyping(f->getFriendID(), false);
|
||||
|
@ -250,76 +255,63 @@ void ChatForm::onFileRecvRequest(ToxFile file)
|
|||
Widget::getInstance()->updateFriendActivity(f);
|
||||
}
|
||||
|
||||
void ChatForm::onAvInvite(uint32_t FriendId, int CallId, bool video)
|
||||
void ChatForm::onAvInvite(uint32_t FriendId, bool video)
|
||||
{
|
||||
if (FriendId != f->getFriendID())
|
||||
return;
|
||||
|
||||
qDebug() << "onAvInvite, callId: " << CallId;
|
||||
qDebug() << "onAvInvite";
|
||||
|
||||
callId = CallId;
|
||||
callButton->disconnect();
|
||||
videoButton->disconnect();
|
||||
disableCallButtons();
|
||||
if (video)
|
||||
{
|
||||
callConfirm = new CallConfirmWidget(videoButton, *f);
|
||||
if (f->getFriendWidget()->chatFormIsSet(false))
|
||||
callConfirm->show();
|
||||
|
||||
connect(callConfirm, &CallConfirmWidget::accepted, this, &ChatForm::onAnswerCallTriggered);
|
||||
connect(callConfirm, &CallConfirmWidget::rejected, this, &ChatForm::onRejectCallTriggered);
|
||||
|
||||
callButton->setObjectName("grey");
|
||||
callButton->setToolTip("");
|
||||
videoButton->setObjectName("yellow");
|
||||
videoButton->setToolTip(tr("Accept video call"));
|
||||
videoButton->style()->polish(videoButton);
|
||||
connect(videoButton, &QPushButton::clicked, this, &ChatForm::onAnswerCallTriggered);
|
||||
}
|
||||
else
|
||||
{
|
||||
callConfirm = new CallConfirmWidget(callButton, *f);
|
||||
if (f->getFriendWidget()->chatFormIsSet(false))
|
||||
callConfirm->show();
|
||||
|
||||
connect(callConfirm, &CallConfirmWidget::accepted, this, &ChatForm::onAnswerCallTriggered);
|
||||
connect(callConfirm, &CallConfirmWidget::rejected, this, &ChatForm::onRejectCallTriggered);
|
||||
|
||||
callButton->setObjectName("yellow");
|
||||
callButton->setToolTip(tr("Accept audio call"));
|
||||
videoButton->setObjectName("grey");
|
||||
videoButton->setToolTip("");
|
||||
callButton->style()->polish(callButton);
|
||||
connect(callButton, &QPushButton::clicked, this, &ChatForm::onAnswerCallTriggered);
|
||||
}
|
||||
|
||||
callButton->style()->polish(callButton);
|
||||
videoButton->style()->polish(videoButton);
|
||||
if (f->getFriendWidget()->chatFormIsSet(false))
|
||||
callConfirm->show();
|
||||
|
||||
connect(callConfirm, &CallConfirmWidget::accepted, this, &ChatForm::onAnswerCallTriggered);
|
||||
connect(callConfirm, &CallConfirmWidget::rejected, this, &ChatForm::onRejectCallTriggered);
|
||||
|
||||
insertChatMessage(ChatMessage::createChatInfoMessage(tr("%1 calling").arg(f->getDisplayedName()),
|
||||
ChatMessage::INFO,
|
||||
QDateTime::currentDateTime()));
|
||||
|
||||
Widget::getInstance()->newFriendMessageAlert(FriendId);
|
||||
Widget::getInstance()->newFriendMessageAlert(FriendId, false);
|
||||
Audio& audio = Audio::getInstance();
|
||||
audio.startLoop();
|
||||
audio.playMono16Sound(":audio/ToxicIncomingCall.pcm");
|
||||
}
|
||||
|
||||
void ChatForm::onAvStart(uint32_t FriendId, int CallId, bool video)
|
||||
void ChatForm::onAvStart(uint32_t FriendId, bool video)
|
||||
{
|
||||
if (FriendId != f->getFriendID())
|
||||
return;
|
||||
|
||||
qDebug() << "onAvStart, callId: " << CallId;
|
||||
qDebug() << "onAvStart";
|
||||
|
||||
audioInputFlag = true;
|
||||
audioOutputFlag = true;
|
||||
callId = CallId;
|
||||
callButton->disconnect();
|
||||
videoButton->disconnect();
|
||||
disableCallButtons();
|
||||
|
||||
if (video)
|
||||
{
|
||||
callButton->setObjectName("grey");
|
||||
callButton->setToolTip("");
|
||||
videoButton->setObjectName("red");
|
||||
videoButton->setToolTip(tr("End video call"));
|
||||
videoButton->style()->polish(videoButton);
|
||||
connect(videoButton, SIGNAL(clicked()),
|
||||
this, SLOT(onHangupCallTriggered()));
|
||||
|
||||
|
@ -329,14 +321,11 @@ void ChatForm::onAvStart(uint32_t FriendId, int CallId, bool video)
|
|||
{
|
||||
callButton->setObjectName("red");
|
||||
callButton->setToolTip(tr("End audio call"));
|
||||
videoButton->setObjectName("grey");
|
||||
videoButton->setToolTip("");
|
||||
callButton->style()->polish(callButton);
|
||||
connect(callButton, SIGNAL(clicked()),
|
||||
this, SLOT(onHangupCallTriggered()));
|
||||
hideNetcam();
|
||||
}
|
||||
callButton->style()->polish(callButton);
|
||||
videoButton->style()->polish(videoButton);
|
||||
|
||||
micButton->setObjectName("green");
|
||||
micButton->style()->polish(micButton);
|
||||
|
@ -353,198 +342,53 @@ void ChatForm::onAvStart(uint32_t FriendId, int CallId, bool video)
|
|||
startCounter();
|
||||
}
|
||||
|
||||
void ChatForm::onAvCancel(uint32_t FriendId, int CallId)
|
||||
void ChatForm::onAvEnd(uint32_t FriendId)
|
||||
{
|
||||
if (FriendId != f->getFriendID())
|
||||
return;
|
||||
|
||||
qDebug() << "onAvCancel, callId: " << CallId;
|
||||
qDebug() << "onAvEnd";
|
||||
|
||||
delete callConfirm;
|
||||
callConfirm = nullptr;
|
||||
|
||||
Audio::getInstance().stopLoop();
|
||||
|
||||
enableCallButtons();
|
||||
stopCounter();
|
||||
|
||||
hideNetcam();
|
||||
|
||||
addSystemInfoMessage(tr("%1 stopped calling").arg(f->getDisplayedName()),
|
||||
ChatMessage::INFO,
|
||||
QDateTime::currentDateTime());
|
||||
}
|
||||
|
||||
void ChatForm::onAvRinging(uint32_t FriendId, int CallId, bool video)
|
||||
void ChatForm::showOutgoingCall(bool video)
|
||||
{
|
||||
if (FriendId != f->getFriendID())
|
||||
return;
|
||||
audioInputFlag = true;
|
||||
audioOutputFlag = true;
|
||||
|
||||
qDebug() << "onAvRinging, callId: " << CallId;
|
||||
|
||||
callId = CallId;
|
||||
callButton->disconnect();
|
||||
videoButton->disconnect();
|
||||
disableCallButtons();
|
||||
if (video)
|
||||
{
|
||||
callButton->setObjectName("grey");
|
||||
callButton->style()->polish(callButton);
|
||||
callButton->setToolTip("");
|
||||
videoButton->setObjectName("yellow");
|
||||
videoButton->style()->polish(videoButton);
|
||||
videoButton->setToolTip(tr("Cancel video call"));
|
||||
connect(videoButton, SIGNAL(clicked()),
|
||||
this, SLOT(onCancelCallTriggered()));
|
||||
connect(videoButton, &QPushButton::clicked,
|
||||
this, &ChatForm::onCancelCallTriggered);
|
||||
}
|
||||
else
|
||||
{
|
||||
callButton->setObjectName("yellow");
|
||||
callButton->style()->polish(callButton);
|
||||
callButton->setToolTip(tr("Cancel audio call"));
|
||||
videoButton->setObjectName("grey");
|
||||
videoButton->style()->polish(videoButton);
|
||||
videoButton->setToolTip("");
|
||||
connect(callButton, SIGNAL(clicked()),
|
||||
this, SLOT(onCancelCallTriggered()));
|
||||
connect(callButton, &QPushButton::clicked,
|
||||
this, &ChatForm::onCancelCallTriggered);
|
||||
}
|
||||
|
||||
addSystemInfoMessage(tr("Calling to %1").arg(f->getDisplayedName()),
|
||||
addSystemInfoMessage(tr("Calling %1").arg(f->getDisplayedName()),
|
||||
ChatMessage::INFO,
|
||||
QDateTime::currentDateTime());
|
||||
|
||||
Widget::getInstance()->updateFriendActivity(f);
|
||||
}
|
||||
|
||||
void ChatForm::onAvStarting(uint32_t FriendId, int CallId, bool video)
|
||||
{
|
||||
if (FriendId != f->getFriendID())
|
||||
return;
|
||||
|
||||
qDebug() << "onAvStarting, callId:" << CallId;
|
||||
|
||||
callId = CallId;
|
||||
|
||||
callButton->disconnect();
|
||||
videoButton->disconnect();
|
||||
if (video)
|
||||
{
|
||||
callButton->setObjectName("grey");
|
||||
callButton->style()->polish(callButton);
|
||||
callButton->setToolTip("");
|
||||
videoButton->setObjectName("red");
|
||||
videoButton->style()->polish(videoButton);
|
||||
videoButton->setToolTip(tr("End video call"));
|
||||
connect(videoButton, SIGNAL(clicked()), this, SLOT(onHangupCallTriggered()));
|
||||
|
||||
showNetcam();
|
||||
}
|
||||
else
|
||||
{
|
||||
callButton->setObjectName("red");
|
||||
callButton->style()->polish(callButton);
|
||||
callButton->setToolTip(tr("End audio call"));
|
||||
videoButton->setObjectName("grey");
|
||||
videoButton->style()->polish(videoButton);
|
||||
videoButton->setToolTip("");
|
||||
connect(callButton, SIGNAL(clicked()), this, SLOT(onHangupCallTriggered()));
|
||||
}
|
||||
|
||||
startCounter();
|
||||
}
|
||||
|
||||
void ChatForm::onAvEnding(uint32_t FriendId, int CallId)
|
||||
{
|
||||
if (FriendId != f->getFriendID())
|
||||
return;
|
||||
|
||||
qDebug() << "onAvEnding, callId: " << CallId;
|
||||
|
||||
delete callConfirm;
|
||||
callConfirm = nullptr;
|
||||
|
||||
enableCallButtons();
|
||||
stopCounter();
|
||||
CameraSource::getInstance().unsubscribe();
|
||||
hideNetcam();
|
||||
}
|
||||
|
||||
void ChatForm::onAvEnd(uint32_t FriendId, int CallId)
|
||||
{
|
||||
if (FriendId != f->getFriendID())
|
||||
return;
|
||||
|
||||
qDebug() << "onAvEnd, callId: " << CallId;
|
||||
|
||||
delete callConfirm;
|
||||
callConfirm = nullptr;
|
||||
|
||||
enableCallButtons();
|
||||
stopCounter();
|
||||
hideNetcam();
|
||||
}
|
||||
|
||||
void ChatForm::onAvRequestTimeout(uint32_t FriendId, int CallId)
|
||||
{
|
||||
if (FriendId != f->getFriendID())
|
||||
return;
|
||||
|
||||
qDebug() << "onAvRequestTimeout, callId: " << CallId;
|
||||
|
||||
delete callConfirm;
|
||||
callConfirm = nullptr;
|
||||
|
||||
enableCallButtons();
|
||||
stopCounter();
|
||||
|
||||
hideNetcam();
|
||||
}
|
||||
|
||||
void ChatForm::onAvPeerTimeout(uint32_t FriendId, int)
|
||||
{
|
||||
if (FriendId != f->getFriendID())
|
||||
return;
|
||||
|
||||
qDebug() << "onAvPeerTimeout";
|
||||
|
||||
delete callConfirm;
|
||||
callConfirm = nullptr;
|
||||
|
||||
enableCallButtons();
|
||||
stopCounter();
|
||||
|
||||
hideNetcam();
|
||||
}
|
||||
|
||||
void ChatForm::onAvRejected(uint32_t FriendId, int)
|
||||
{
|
||||
if (FriendId != f->getFriendID())
|
||||
return;
|
||||
|
||||
qDebug() << "onAvRejected";
|
||||
|
||||
delete callConfirm;
|
||||
callConfirm = nullptr;
|
||||
|
||||
enableCallButtons();
|
||||
|
||||
insertChatMessage(ChatMessage::createChatInfoMessage(tr("Call rejected"),
|
||||
ChatMessage::INFO,
|
||||
QDateTime::currentDateTime()));
|
||||
|
||||
hideNetcam();
|
||||
}
|
||||
|
||||
void ChatForm::onAvMediaChange(uint32_t FriendId, int CallId, bool video)
|
||||
{
|
||||
if (FriendId != f->getFriendID() || CallId != callId)
|
||||
return;
|
||||
|
||||
qDebug() << "onAvMediaChange, callId: " << CallId;
|
||||
|
||||
if (video)
|
||||
showNetcam();
|
||||
else
|
||||
hideNetcam();
|
||||
}
|
||||
|
||||
void ChatForm::onAnswerCallTriggered()
|
||||
{
|
||||
qDebug() << "onAnswerCallTriggered";
|
||||
|
@ -555,9 +399,19 @@ void ChatForm::onAnswerCallTriggered()
|
|||
callConfirm = nullptr;
|
||||
}
|
||||
|
||||
audioInputFlag = true;
|
||||
audioOutputFlag = true;
|
||||
emit answerCall(callId);
|
||||
Audio::getInstance().stopLoop();
|
||||
|
||||
disableCallButtons();
|
||||
|
||||
if (!coreav->answerCall(f->getFriendID()))
|
||||
{
|
||||
enableCallButtons();
|
||||
stopCounter();
|
||||
hideNetcam();
|
||||
return;
|
||||
}
|
||||
|
||||
onAvStart(f->getFriendID(), coreav->isCallVideoEnabled(f->getFriendID()));
|
||||
}
|
||||
|
||||
void ChatForm::onHangupCallTriggered()
|
||||
|
@ -570,9 +424,11 @@ void ChatForm::onHangupCallTriggered()
|
|||
|
||||
audioInputFlag = false;
|
||||
audioOutputFlag = false;
|
||||
emit hangupCall(callId);
|
||||
coreav->cancelCall(f->getFriendID());
|
||||
|
||||
stopCounter();
|
||||
enableCallButtons();
|
||||
hideNetcam();
|
||||
}
|
||||
|
||||
void ChatForm::onRejectCallTriggered()
|
||||
|
@ -585,56 +441,48 @@ void ChatForm::onRejectCallTriggered()
|
|||
callConfirm = nullptr;
|
||||
}
|
||||
|
||||
Audio::getInstance().stopLoop();
|
||||
|
||||
audioInputFlag = false;
|
||||
audioOutputFlag = false;
|
||||
emit rejectCall(callId);
|
||||
coreav->cancelCall(f->getFriendID());
|
||||
|
||||
enableCallButtons();
|
||||
stopCounter();
|
||||
}
|
||||
|
||||
void ChatForm::onCallTriggered()
|
||||
{
|
||||
qDebug() << "onCallTriggered";
|
||||
|
||||
audioInputFlag = true;
|
||||
audioOutputFlag = true;
|
||||
callButton->disconnect();
|
||||
videoButton->disconnect();
|
||||
emit startCall(f->getFriendID(), false);
|
||||
disableCallButtons();
|
||||
if (coreav->startCall(f->getFriendID(), false))
|
||||
showOutgoingCall(false);
|
||||
else
|
||||
enableCallButtons();
|
||||
}
|
||||
|
||||
void ChatForm::onVideoCallTriggered()
|
||||
{
|
||||
qDebug() << "onVideoCallTriggered";
|
||||
|
||||
audioInputFlag = true;
|
||||
audioOutputFlag = true;
|
||||
callButton->disconnect();
|
||||
videoButton->disconnect();
|
||||
emit startCall(f->getFriendID(), true);
|
||||
}
|
||||
|
||||
void ChatForm::onAvCallFailed(uint32_t FriendId)
|
||||
{
|
||||
if (FriendId != f->getFriendID())
|
||||
return;
|
||||
|
||||
qDebug() << "onAvCallFailed";
|
||||
|
||||
delete callConfirm;
|
||||
callConfirm = nullptr;
|
||||
|
||||
enableCallButtons();
|
||||
disableCallButtons();
|
||||
if (coreav->startCall(f->getFriendID(), true))
|
||||
showOutgoingCall(true);
|
||||
else
|
||||
enableCallButtons();
|
||||
}
|
||||
|
||||
void ChatForm::onCancelCallTriggered()
|
||||
{
|
||||
qDebug() << "onCancelCallTriggered";
|
||||
|
||||
enableCallButtons();
|
||||
if (!coreav->cancelCall(f->getFriendID()))
|
||||
qWarning() << "Failed to cancel a call! Assuming we're not in call";
|
||||
|
||||
enableCallButtons();
|
||||
stopCounter();
|
||||
hideNetcam();
|
||||
emit cancelCall(callId, f->getFriendID());
|
||||
}
|
||||
|
||||
void ChatForm::enableCallButtons()
|
||||
|
@ -644,6 +492,31 @@ void ChatForm::enableCallButtons()
|
|||
audioInputFlag = false;
|
||||
audioOutputFlag = false;
|
||||
|
||||
disableCallButtons();
|
||||
|
||||
if (disableCallButtonsTimer == nullptr)
|
||||
{
|
||||
disableCallButtonsTimer = new QTimer();
|
||||
connect(disableCallButtonsTimer, SIGNAL(timeout()),
|
||||
this, SLOT(onEnableCallButtons()));
|
||||
disableCallButtonsTimer->start(1500); // 1.5sec
|
||||
qDebug() << "timer started!!";
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
void ChatForm::disableCallButtons()
|
||||
{
|
||||
qDebug() << "disableCallButtons";
|
||||
|
||||
// Prevents race enable / disable / onEnable, when it should be disabled
|
||||
if (disableCallButtonsTimer)
|
||||
{
|
||||
disableCallButtonsTimer->stop();
|
||||
delete disableCallButtonsTimer;
|
||||
disableCallButtonsTimer = nullptr;
|
||||
}
|
||||
|
||||
micButton->setObjectName("grey");
|
||||
micButton->style()->polish(micButton);
|
||||
micButton->setToolTip("");
|
||||
|
@ -661,16 +534,6 @@ void ChatForm::enableCallButtons()
|
|||
videoButton->style()->polish(videoButton);
|
||||
videoButton->setToolTip("");
|
||||
videoButton->disconnect();
|
||||
|
||||
if (disableCallButtonsTimer == nullptr)
|
||||
{
|
||||
disableCallButtonsTimer = new QTimer();
|
||||
connect(disableCallButtonsTimer, SIGNAL(timeout()),
|
||||
this, SLOT(onEnableCallButtons()));
|
||||
disableCallButtonsTimer->start(1500); // 1.5sec
|
||||
qDebug() << "timer started!!";
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
void ChatForm::onEnableCallButtons()
|
||||
|
@ -700,7 +563,7 @@ void ChatForm::onMicMuteToggle()
|
|||
{
|
||||
if (audioInputFlag == true)
|
||||
{
|
||||
emit micMuteToggle(callId);
|
||||
coreav->micMuteToggle(f->getFriendID());
|
||||
if (micButton->objectName() == "red")
|
||||
{
|
||||
micButton->setObjectName("green");
|
||||
|
@ -720,7 +583,7 @@ void ChatForm::onVolMuteToggle()
|
|||
{
|
||||
if (audioOutputFlag == true)
|
||||
{
|
||||
emit volMuteToggle(callId);
|
||||
coreav->volMuteToggle(f->getFriendID());
|
||||
if (volButton->objectName() == "red")
|
||||
{
|
||||
volButton->setObjectName("green");
|
||||
|
@ -756,7 +619,7 @@ GenericNetCamView *ChatForm::createNetcam()
|
|||
{
|
||||
qDebug() << "creating netcam";
|
||||
NetCamView* view = new NetCamView(f->getFriendID(), this);
|
||||
view->show(Core::getInstance()->getVideoSourceFromCall(callId), f->getDisplayedName());
|
||||
view->show(Core::getInstance()->getAv()->getVideoSourceFromCall(f->getFriendID()), f->getDisplayedName());
|
||||
return view;
|
||||
}
|
||||
|
||||
|
|
|
@ -34,6 +34,7 @@ class CallConfirmWidget;
|
|||
class QHideEvent;
|
||||
class QMoveEvent;
|
||||
class OfflineMsgEngine;
|
||||
class CoreAV;
|
||||
|
||||
class ChatForm : public GenericChatForm
|
||||
{
|
||||
|
@ -52,32 +53,14 @@ public:
|
|||
|
||||
signals:
|
||||
void sendFile(uint32_t friendId, QString, QString, long long);
|
||||
void startCall(uint32_t FriendId, bool video);
|
||||
void answerCall(int callId);
|
||||
void hangupCall(int callId);
|
||||
void cancelCall(int callId, uint32_t FriendId);
|
||||
void rejectCall(int callId);
|
||||
void micMuteToggle(int callId);
|
||||
void volMuteToggle(int callId);
|
||||
void aliasChanged(const QString& alias);
|
||||
|
||||
public slots:
|
||||
void startFileSend(ToxFile file);
|
||||
void onFileRecvRequest(ToxFile file);
|
||||
void onAvInvite(uint32_t FriendId, int CallId, bool video);
|
||||
void onAvStart(uint32_t FriendId, int CallId, bool video);
|
||||
void onAvCancel(uint32_t FriendId, int CallId);
|
||||
void onAvEnd(uint32_t FriendId, int CallId);
|
||||
void onAvRinging(uint32_t FriendId, int CallId, bool video);
|
||||
void onAvStarting(uint32_t FriendId, int CallId, bool video);
|
||||
void onAvEnding(uint32_t FriendId, int CallId);
|
||||
void onAvRequestTimeout(uint32_t FriendId, int CallId);
|
||||
void onAvPeerTimeout(uint32_t FriendId, int CallId);
|
||||
void onAvMediaChange(uint32_t FriendId, int CallId, bool video);
|
||||
void onAvCallFailed(uint32_t FriendId);
|
||||
void onAvRejected(uint32_t FriendId, int CallId);
|
||||
void onMicMuteToggle();
|
||||
void onVolMuteToggle();
|
||||
void onAvInvite(uint32_t FriendId, bool video);
|
||||
void onAvStart(uint32_t FriendId, bool video);
|
||||
void onAvEnd(uint32_t FriendId);
|
||||
void onAvatarChange(uint32_t FriendId, const QPixmap& pic);
|
||||
void onAvatarRemoved(uint32_t FriendId);
|
||||
|
||||
|
@ -91,6 +74,8 @@ private slots:
|
|||
void onHangupCallTriggered();
|
||||
void onCancelCallTriggered();
|
||||
void onRejectCallTriggered();
|
||||
void onMicMuteToggle();
|
||||
void onVolMuteToggle();
|
||||
void onFileSendFailed(uint32_t FriendId, const QString &fname);
|
||||
void onLoadHistory();
|
||||
void onUpdateTime();
|
||||
|
@ -102,6 +87,7 @@ private slots:
|
|||
|
||||
private:
|
||||
void retranslateUi();
|
||||
void showOutgoingCall(bool video);
|
||||
|
||||
protected:
|
||||
virtual GenericNetCamView* createNetcam() final override;
|
||||
|
@ -112,9 +98,9 @@ protected:
|
|||
virtual void showEvent(QShowEvent* event) final override;
|
||||
|
||||
private:
|
||||
CoreAV* coreav;
|
||||
Friend* f;
|
||||
CroppingLabel *statusMessageLabel;
|
||||
int callId;
|
||||
QLabel *callDuration;
|
||||
QTimer *callDurationTimer;
|
||||
QTimer typingTimer;
|
||||
|
@ -129,6 +115,7 @@ private:
|
|||
QString secondsToDHMS(quint32 duration);
|
||||
CallConfirmWidget *callConfirm;
|
||||
void enableCallButtons();
|
||||
void disableCallButtons();
|
||||
bool isTyping;
|
||||
void SendMessageStr(QString msg);
|
||||
};
|
||||
|
|
|
@ -25,6 +25,7 @@
|
|||
#include "src/widget/tool/croppinglabel.h"
|
||||
#include "src/widget/maskablepixmapwidget.h"
|
||||
#include "src/core/core.h"
|
||||
#include "src/core/coreav.h"
|
||||
#include "src/widget/style.h"
|
||||
#include "src/persistence/historykeeper.h"
|
||||
#include "src/widget/flowlayout.h"
|
||||
|
@ -282,13 +283,13 @@ void GroupChatForm::onMicMuteToggle()
|
|||
{
|
||||
if (micButton->objectName() == "red")
|
||||
{
|
||||
Core::getInstance()->enableGroupCallMic(group->getGroupId());
|
||||
Core::getInstance()->getAv()->enableGroupCallMic(group->getGroupId());
|
||||
micButton->setObjectName("green");
|
||||
micButton->setToolTip(tr("Mute microphone"));
|
||||
}
|
||||
else
|
||||
{
|
||||
Core::getInstance()->disableGroupCallMic(group->getGroupId());
|
||||
Core::getInstance()->getAv()->disableGroupCallMic(group->getGroupId());
|
||||
micButton->setObjectName("red");
|
||||
micButton->setToolTip(tr("Unmute microphone"));
|
||||
}
|
||||
|
@ -303,13 +304,13 @@ void GroupChatForm::onVolMuteToggle()
|
|||
{
|
||||
if (volButton->objectName() == "red")
|
||||
{
|
||||
Core::getInstance()->enableGroupCallVol(group->getGroupId());
|
||||
Core::getInstance()->getAv()->enableGroupCallVol(group->getGroupId());
|
||||
volButton->setObjectName("green");
|
||||
volButton->setToolTip(tr("Mute call"));
|
||||
}
|
||||
else
|
||||
{
|
||||
Core::getInstance()->disableGroupCallVol(group->getGroupId());
|
||||
Core::getInstance()->getAv()->disableGroupCallVol(group->getGroupId());
|
||||
volButton->setObjectName("red");
|
||||
volButton->setToolTip(tr("Unmute call"));
|
||||
}
|
||||
|
@ -322,7 +323,7 @@ void GroupChatForm::onCallClicked()
|
|||
{
|
||||
if (!inCall)
|
||||
{
|
||||
Core::getInstance()->joinGroupCall(group->getGroupId());
|
||||
Core::getInstance()->getAv()->joinGroupCall(group->getGroupId());
|
||||
audioInputFlag = true;
|
||||
audioOutputFlag = true;
|
||||
callButton->setObjectName("red");
|
||||
|
@ -339,7 +340,7 @@ void GroupChatForm::onCallClicked()
|
|||
}
|
||||
else
|
||||
{
|
||||
Core::getInstance()->leaveGroupCall(group->getGroupId());
|
||||
Core::getInstance()->getAv()->leaveGroupCall(group->getGroupId());
|
||||
audioInputFlag = false;
|
||||
audioOutputFlag = false;
|
||||
callButton->setObjectName("green");
|
||||
|
@ -375,10 +376,9 @@ void GroupChatForm::keyPressEvent(QKeyEvent* ev)
|
|||
// Push to talk (CTRL+P)
|
||||
if (ev->key() == Qt::Key_P && (ev->modifiers() & Qt::ControlModifier) && inCall)
|
||||
{
|
||||
Core* core = Core::getInstance();
|
||||
if (!core->isGroupCallMicEnabled(group->getGroupId()))
|
||||
if (!Core::getInstance()->getAv()->isGroupCallMicEnabled(group->getGroupId()))
|
||||
{
|
||||
core->enableGroupCallMic(group->getGroupId());
|
||||
Core::getInstance()->getAv()->enableGroupCallMic(group->getGroupId());
|
||||
micButton->setObjectName("green");
|
||||
micButton->style()->polish(micButton);
|
||||
Style::repolish(micButton);
|
||||
|
@ -394,10 +394,9 @@ void GroupChatForm::keyReleaseEvent(QKeyEvent* ev)
|
|||
// Push to talk (CTRL+P)
|
||||
if (ev->key() == Qt::Key_P && (ev->modifiers() & Qt::ControlModifier) && inCall)
|
||||
{
|
||||
Core* core = Core::getInstance();
|
||||
if (core->isGroupCallMicEnabled(group->getGroupId()))
|
||||
if (Core::getInstance()->getAv()->isGroupCallMicEnabled(group->getGroupId()))
|
||||
{
|
||||
core->disableGroupCallMic(group->getGroupId());
|
||||
Core::getInstance()->getAv()->disableGroupCallMic(group->getGroupId());
|
||||
micButton->setObjectName("red");
|
||||
micButton->style()->polish(micButton);
|
||||
Style::repolish(micButton);
|
||||
|
|
|
@ -25,6 +25,8 @@
|
|||
#include "src/video/cameradevice.h"
|
||||
#include "src/video/videosurface.h"
|
||||
#include "src/widget/translator.h"
|
||||
#include "src/core/core.h"
|
||||
#include "src/core/coreav.h"
|
||||
|
||||
#if defined(__APPLE__) && defined(__MACH__)
|
||||
#include <OpenAL/al.h>
|
||||
|
@ -202,14 +204,15 @@ void AVForm::onVideoDevChanged(int index)
|
|||
qWarning() << "Invalid index";
|
||||
return;
|
||||
}
|
||||
|
||||
QString dev = videoDeviceList[index].first;
|
||||
Settings::getInstance().setVideoDev(dev);
|
||||
bool previouslyBlocked = bodyUI->videoModescomboBox->blockSignals(true);
|
||||
updateVideoModes(index);
|
||||
bodyUI->videoModescomboBox->blockSignals(previouslyBlocked);
|
||||
camera.open(dev);
|
||||
killVideoSurface();
|
||||
createVideoSurface();
|
||||
if (dev == "none")
|
||||
Core::getInstance()->getAv()->sendNoVideo();
|
||||
}
|
||||
|
||||
void AVForm::hideEvent(QHideEvent *)
|
||||
|
@ -316,8 +319,8 @@ void AVForm::onInDevChanged(const QString &deviceDescriptor)
|
|||
Settings::getInstance().setInDev(deviceDescriptor);
|
||||
|
||||
Audio& audio = Audio::getInstance();
|
||||
audio.unsubscribeInput();
|
||||
audio.subscribeInput();
|
||||
if (audio.isInputReady())
|
||||
audio.openInput(deviceDescriptor);
|
||||
}
|
||||
|
||||
void AVForm::onOutDevChanged(const QString& deviceDescriptor)
|
||||
|
@ -325,8 +328,8 @@ void AVForm::onOutDevChanged(const QString& deviceDescriptor)
|
|||
Settings::getInstance().setOutDev(deviceDescriptor);
|
||||
|
||||
Audio& audio = Audio::getInstance();
|
||||
audio.unsubscribeInput();
|
||||
audio.subscribeInput();
|
||||
if (audio.isOutputReady())
|
||||
audio.openOutput(deviceDescriptor);
|
||||
}
|
||||
|
||||
void AVForm::onFilterAudioToggled(bool filterAudio)
|
||||
|
|
|
@ -24,6 +24,7 @@
|
|||
#include "src/persistence/settings.h"
|
||||
#include "src/persistence/smileypack.h"
|
||||
#include "src/core/core.h"
|
||||
#include "src/core/coreav.h"
|
||||
#include "src/widget/style.h"
|
||||
#include "src/nexus.h"
|
||||
#include "src/persistence/profile.h"
|
||||
|
@ -358,7 +359,7 @@ void GeneralForm::onUseProxyUpdated()
|
|||
|
||||
void GeneralForm::onReconnectClicked()
|
||||
{
|
||||
if (Core::getInstance()->anyActiveCalls())
|
||||
if (Core::getInstance()->getAv()->anyActiveCalls())
|
||||
{
|
||||
QMessageBox::warning(this, tr("Call active", "popup title"),
|
||||
tr("You can't disconnect while a call is active!", "popup text"));
|
||||
|
|
|
@ -25,7 +25,7 @@ class GenericForm : public QWidget
|
|||
Q_OBJECT
|
||||
public:
|
||||
GenericForm(const QPixmap &icon) : formIcon(icon) {;}
|
||||
~GenericForm() {}
|
||||
virtual ~GenericForm() {}
|
||||
|
||||
virtual QString getFormName() = 0;
|
||||
QPixmap getFormIcon() {return formIcon;}
|
||||
|
|
|
@ -27,6 +27,7 @@ class AdjustingScrollArea : public QScrollArea
|
|||
Q_OBJECT
|
||||
public:
|
||||
explicit AdjustingScrollArea(QWidget *parent = 0);
|
||||
virtual ~AdjustingScrollArea() = default;
|
||||
|
||||
protected:
|
||||
virtual void resizeEvent(QResizeEvent *ev) override;
|
||||
|
|
|
@ -60,18 +60,15 @@ void MicFeedbackWidget::paintEvent(QPaintEvent*)
|
|||
|
||||
void MicFeedbackWidget::timerEvent(QTimerEvent*)
|
||||
{
|
||||
const int framesize = (20 * 48000) / 1000;
|
||||
const int bufsize = framesize * 2;
|
||||
uint8_t buff[bufsize];
|
||||
memset(buff, 0, bufsize);
|
||||
const int framesize = AUDIO_FRAME_SAMPLE_COUNT * AUDIO_CHANNELS;
|
||||
int16_t buff[framesize] = {0};
|
||||
|
||||
if (Audio::getInstance().tryCaptureSamples(buff, framesize))
|
||||
if (Audio::getInstance().tryCaptureSamples(buff, AUDIO_FRAME_SAMPLE_COUNT))
|
||||
{
|
||||
double max = 0;
|
||||
int16_t* buffReal = reinterpret_cast<int16_t*>(&buff[0]);
|
||||
|
||||
for (int i = 0; i < bufsize / 2; ++i)
|
||||
max = std::max(max, fabs(buffReal[i] / 32767.0));
|
||||
for (int i = 0; i < framesize; ++i)
|
||||
max = std::max(max, fabs(buff[i] / 32767.0));
|
||||
|
||||
if (max > current)
|
||||
current = max;
|
||||
|
|
|
@ -21,6 +21,7 @@
|
|||
#include "contentlayout.h"
|
||||
#include "ui_mainwindow.h"
|
||||
#include "src/core/core.h"
|
||||
#include "src/core/coreav.h"
|
||||
#include "src/persistence/settings.h"
|
||||
#include "contentdialog.h"
|
||||
#include "src/friend.h"
|
||||
|
@ -881,6 +882,7 @@ void Widget::addFriend(int friendId, const QString &userId)
|
|||
contactListWidget->addFriendWidget(newfriend->getFriendWidget(),Status::Offline,Settings::getInstance().getFriendCircleID(newfriend->getToxId()));
|
||||
|
||||
Core* core = Nexus::getCore();
|
||||
CoreAV* coreav = core->getAv();
|
||||
connect(newfriend, &Friend::displayedNameChanged, this, &Widget::onFriendDisplayChanged);
|
||||
connect(settingsWidget, &SettingsWidget::compactToggled, newfriend->getFriendWidget(), &GenericChatroomWidget::compactChange);
|
||||
connect(newfriend->getFriendWidget(), SIGNAL(chatroomWidgetClicked(GenericChatroomWidget*,bool)), this, SLOT(onChatroomWidgetClicked(GenericChatroomWidget*,bool)));
|
||||
|
@ -890,27 +892,11 @@ void Widget::addFriend(int friendId, const QString &userId)
|
|||
connect(newfriend->getChatForm(), &GenericChatForm::sendMessage, core, &Core::sendMessage);
|
||||
connect(newfriend->getChatForm(), &GenericChatForm::sendAction, core, &Core::sendAction);
|
||||
connect(newfriend->getChatForm(), &ChatForm::sendFile, core, &Core::sendFile);
|
||||
connect(newfriend->getChatForm(), &ChatForm::answerCall, core, &Core::answerCall);
|
||||
connect(newfriend->getChatForm(), &ChatForm::hangupCall, core, &Core::hangupCall);
|
||||
connect(newfriend->getChatForm(), &ChatForm::rejectCall, core, &Core::rejectCall);
|
||||
connect(newfriend->getChatForm(), &ChatForm::startCall, core, &Core::startCall);
|
||||
connect(newfriend->getChatForm(), &ChatForm::cancelCall, core, &Core::cancelCall);
|
||||
connect(newfriend->getChatForm(), &ChatForm::micMuteToggle, core, &Core::micMuteToggle);
|
||||
connect(newfriend->getChatForm(), &ChatForm::volMuteToggle, core, &Core::volMuteToggle);
|
||||
connect(newfriend->getChatForm(), &ChatForm::aliasChanged, newfriend->getFriendWidget(), &FriendWidget::setAlias);
|
||||
connect(core, &Core::fileReceiveRequested, newfriend->getChatForm(), &ChatForm::onFileRecvRequest);
|
||||
connect(core, &Core::avInvite, newfriend->getChatForm(), &ChatForm::onAvInvite);
|
||||
connect(core, &Core::avStart, newfriend->getChatForm(), &ChatForm::onAvStart);
|
||||
connect(core, &Core::avCancel, newfriend->getChatForm(), &ChatForm::onAvCancel);
|
||||
connect(core, &Core::avEnd, newfriend->getChatForm(), &ChatForm::onAvEnd);
|
||||
connect(core, &Core::avRinging, newfriend->getChatForm(), &ChatForm::onAvRinging);
|
||||
connect(core, &Core::avStarting, newfriend->getChatForm(), &ChatForm::onAvStarting);
|
||||
connect(core, &Core::avEnding, newfriend->getChatForm(), &ChatForm::onAvEnding);
|
||||
connect(core, &Core::avRequestTimeout, newfriend->getChatForm(), &ChatForm::onAvRequestTimeout);
|
||||
connect(core, &Core::avPeerTimeout, newfriend->getChatForm(), &ChatForm::onAvPeerTimeout);
|
||||
connect(core, &Core::avMediaChange, newfriend->getChatForm(), &ChatForm::onAvMediaChange);
|
||||
connect(core, &Core::avCallFailed, newfriend->getChatForm(), &ChatForm::onAvCallFailed);
|
||||
connect(core, &Core::avRejected, newfriend->getChatForm(), &ChatForm::onAvRejected);
|
||||
connect(coreav, &CoreAV::avInvite, newfriend->getChatForm(), &ChatForm::onAvInvite, Qt::BlockingQueuedConnection);
|
||||
connect(coreav, &CoreAV::avStart, newfriend->getChatForm(), &ChatForm::onAvStart, Qt::BlockingQueuedConnection);
|
||||
connect(coreav, &CoreAV::avEnd, newfriend->getChatForm(), &ChatForm::onAvEnd, Qt::BlockingQueuedConnection);
|
||||
connect(core, &Core::friendAvatarChanged, newfriend->getChatForm(), &ChatForm::onAvatarChange);
|
||||
connect(core, &Core::friendAvatarChanged, newfriend->getFriendWidget(), &FriendWidget::onAvatarChange);
|
||||
connect(core, &Core::friendAvatarRemoved, newfriend->getChatForm(), &ChatForm::onAvatarRemoved);
|
||||
|
@ -1132,7 +1118,7 @@ void Widget::addGroupDialog(Group *group, ContentDialog *dialog)
|
|||
connect(groupWidget, SIGNAL(chatroomWidgetClicked(GenericChatroomWidget*)), group->getChatForm(), SLOT(focusInput()));
|
||||
}
|
||||
|
||||
bool Widget::newFriendMessageAlert(int friendId)
|
||||
bool Widget::newFriendMessageAlert(int friendId, bool sound)
|
||||
{
|
||||
bool hasActive;
|
||||
QWidget* currentWindow;
|
||||
|
@ -1150,7 +1136,7 @@ bool Widget::newFriendMessageAlert(int friendId)
|
|||
hasActive = f->getFriendWidget() == activeChatroomWidget;
|
||||
}
|
||||
|
||||
if (newMessageAlert(currentWindow, hasActive))
|
||||
if (newMessageAlert(currentWindow, hasActive, sound))
|
||||
{
|
||||
f->setEventFlag(true);
|
||||
f->getFriendWidget()->updateStatusLight();
|
||||
|
@ -1189,7 +1175,7 @@ bool Widget::newGroupMessageAlert(int groupId, bool notify)
|
|||
hasActive = g->getGroupWidget() == activeChatroomWidget;
|
||||
}
|
||||
|
||||
if (newMessageAlert(currentWindow, hasActive, notify))
|
||||
if (newMessageAlert(currentWindow, hasActive, true, notify))
|
||||
{
|
||||
g->setEventFlag(true);
|
||||
g->getGroupWidget()->updateStatusLight();
|
||||
|
@ -1227,7 +1213,7 @@ QString Widget::fromDialogType(DialogType type)
|
|||
}
|
||||
}
|
||||
|
||||
bool Widget::newMessageAlert(QWidget* currentWindow, bool isActive, bool notify)
|
||||
bool Widget::newMessageAlert(QWidget* currentWindow, bool isActive, bool sound, bool notify)
|
||||
{
|
||||
bool inactiveWindow = isMinimized() || !currentWindow->isActiveWindow();
|
||||
|
||||
|
@ -1249,43 +1235,13 @@ bool Widget::newMessageAlert(QWidget* currentWindow, bool isActive, bool notify)
|
|||
currentWindow->activateWindow();
|
||||
}
|
||||
|
||||
if (Settings::getInstance().getNotifySound())
|
||||
{
|
||||
static QFile sndFile(":audio/notification.pcm");
|
||||
static QByteArray sndData;
|
||||
|
||||
if (sndData.isEmpty())
|
||||
{
|
||||
sndFile.open(QIODevice::ReadOnly);
|
||||
sndData = sndFile.readAll();
|
||||
sndFile.close();
|
||||
}
|
||||
|
||||
Audio::getInstance().playMono16Sound(sndData);
|
||||
}
|
||||
if (Settings::getInstance().getNotifySound() && sound)
|
||||
Audio::getInstance().playMono16Sound(":audio/notification.pcm");
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void Widget::playRingtone()
|
||||
{
|
||||
if (ui->statusButton->property("status").toString() == "busy")
|
||||
return;
|
||||
|
||||
// for whatever reason this plays slower/downshifted from what any other program plays the file as... but whatever
|
||||
static QFile sndFile1(":audio/ToxicIncomingCall.pcm");
|
||||
static QByteArray sndData1;
|
||||
if (sndData1.isEmpty())
|
||||
{
|
||||
sndFile1.open(QIODevice::ReadOnly);
|
||||
sndData1 = sndFile1.readAll();
|
||||
sndFile1.close();
|
||||
}
|
||||
|
||||
Audio::getInstance().playMono16Sound(sndData1);
|
||||
}
|
||||
|
||||
void Widget::onFriendRequestReceived(const QString& userId, const QString& message)
|
||||
{
|
||||
FriendRequestDialog dialog(this, userId, message);
|
||||
|
@ -1588,7 +1544,7 @@ Group *Widget::createGroup(int groupId)
|
|||
Core* core = Nexus::getCore();
|
||||
|
||||
QString groupName = QString("Groupchat #%1").arg(groupId);
|
||||
Group* newgroup = GroupList::addGroup(groupId, groupName, core->isGroupAvEnabled(groupId));
|
||||
Group* newgroup = GroupList::addGroup(groupId, groupName, core->getAv()->isGroupAvEnabled(groupId));
|
||||
|
||||
contactListWidget->addGroupWidget(newgroup->getGroupWidget());
|
||||
newgroup->getGroupWidget()->updateStatusLight();
|
||||
|
|
|
@ -67,7 +67,7 @@ public:
|
|||
static Widget* getInstance();
|
||||
void addFriendDialog(Friend* frnd, ContentDialog* dialog);
|
||||
void addGroupDialog(Group* group, ContentDialog* dialog);
|
||||
bool newFriendMessageAlert(int friendId);
|
||||
bool newFriendMessageAlert(int friendId, bool sound=true);
|
||||
bool newGroupMessageAlert(int groupId, bool notify);
|
||||
bool getIsWindowMinimized();
|
||||
void updateIcons();
|
||||
|
@ -134,7 +134,6 @@ public slots:
|
|||
void onGroupTitleChanged(int groupnumber, const QString& author, const QString& title);
|
||||
void onGroupPeerAudioPlaying(int groupnumber, int peernumber);
|
||||
void onGroupSendResult(int groupId, const QString& message, int result);
|
||||
void playRingtone();
|
||||
void onFriendTypingChanged(int friendId, bool isTyping);
|
||||
void nextContact();
|
||||
void previousContact();
|
||||
|
@ -204,7 +203,7 @@ private:
|
|||
};
|
||||
|
||||
private:
|
||||
bool newMessageAlert(QWidget* currentWindow, bool isActive, bool notify = true);
|
||||
bool newMessageAlert(QWidget* currentWindow, bool isActive, bool sound = true, bool notify = true);
|
||||
void setActiveToolMenuButton(ActiveToolMenuButton newActiveButton);
|
||||
void hideMainForms(GenericChatroomWidget* chatroomWidget);
|
||||
Group *createGroup(int groupId);
|
||||
|
|
Loading…
Reference in New Issue
Block a user