mirror of
https://github.com/overte-org/overte.git
synced 2025-06-22 08:00:46 +02:00
Merge branch 'master' of github.com:highfidelity/hifi into tablet-ui-edit-js
This commit is contained in:
commit
a19bc31a78
15 changed files with 347 additions and 211 deletions
|
@ -47,40 +47,54 @@ static const QString AUDIO_THREADING_GROUP_KEY = "audio_threading";
|
||||||
int AudioMixer::_numStaticJitterFrames{ -1 };
|
int AudioMixer::_numStaticJitterFrames{ -1 };
|
||||||
float AudioMixer::_noiseMutingThreshold{ DEFAULT_NOISE_MUTING_THRESHOLD };
|
float AudioMixer::_noiseMutingThreshold{ DEFAULT_NOISE_MUTING_THRESHOLD };
|
||||||
float AudioMixer::_attenuationPerDoublingInDistance{ DEFAULT_ATTENUATION_PER_DOUBLING_IN_DISTANCE };
|
float AudioMixer::_attenuationPerDoublingInDistance{ DEFAULT_ATTENUATION_PER_DOUBLING_IN_DISTANCE };
|
||||||
|
std::map<QString, std::shared_ptr<CodecPlugin>> AudioMixer::_availableCodecs{ };
|
||||||
|
QStringList AudioMixer::_codecPreferenceOrder{};
|
||||||
QHash<QString, AABox> AudioMixer::_audioZones;
|
QHash<QString, AABox> AudioMixer::_audioZones;
|
||||||
QVector<AudioMixer::ZoneSettings> AudioMixer::_zoneSettings;
|
QVector<AudioMixer::ZoneSettings> AudioMixer::_zoneSettings;
|
||||||
QVector<AudioMixer::ReverbSettings> AudioMixer::_zoneReverbSettings;
|
QVector<AudioMixer::ReverbSettings> AudioMixer::_zoneReverbSettings;
|
||||||
|
|
||||||
AudioMixer::AudioMixer(ReceivedMessage& message) :
|
AudioMixer::AudioMixer(ReceivedMessage& message) :
|
||||||
ThreadedAssignment(message) {
|
ThreadedAssignment(message) {
|
||||||
|
|
||||||
|
// hash the available codecs (on the mixer)
|
||||||
|
auto codecPlugins = PluginManager::getInstance()->getCodecPlugins();
|
||||||
|
std::for_each(codecPlugins.cbegin(), codecPlugins.cend(),
|
||||||
|
[&](const CodecPluginPointer& codec) {
|
||||||
|
_availableCodecs[codec->getName()] = codec;
|
||||||
|
});
|
||||||
|
|
||||||
auto nodeList = DependencyManager::get<NodeList>();
|
auto nodeList = DependencyManager::get<NodeList>();
|
||||||
auto& packetReceiver = nodeList->getPacketReceiver();
|
auto& packetReceiver = nodeList->getPacketReceiver();
|
||||||
|
|
||||||
packetReceiver.registerListenerForTypes({ PacketType::MicrophoneAudioNoEcho, PacketType::MicrophoneAudioWithEcho,
|
// packets whose consequences are limited to their own node can be parallelized
|
||||||
PacketType::InjectAudio, PacketType::AudioStreamStats },
|
packetReceiver.registerListenerForTypes({
|
||||||
this, "handleAudioPacket");
|
PacketType::MicrophoneAudioNoEcho,
|
||||||
packetReceiver.registerListenerForTypes({ PacketType::SilentAudioFrame }, this, "handleSilentAudioPacket");
|
PacketType::MicrophoneAudioWithEcho,
|
||||||
packetReceiver.registerListener(PacketType::NegotiateAudioFormat, this, "handleNegotiateAudioFormat");
|
PacketType::InjectAudio,
|
||||||
|
PacketType::AudioStreamStats,
|
||||||
|
PacketType::SilentAudioFrame,
|
||||||
|
PacketType::NegotiateAudioFormat,
|
||||||
|
PacketType::MuteEnvironment,
|
||||||
|
PacketType::NodeIgnoreRequest,
|
||||||
|
PacketType::RadiusIgnoreRequest,
|
||||||
|
PacketType::RequestsDomainListData,
|
||||||
|
PacketType::PerAvatarGainSet },
|
||||||
|
this, "queueAudioPacket");
|
||||||
|
|
||||||
|
// packets whose consequences are global should be processed on the main thread
|
||||||
packetReceiver.registerListener(PacketType::MuteEnvironment, this, "handleMuteEnvironmentPacket");
|
packetReceiver.registerListener(PacketType::MuteEnvironment, this, "handleMuteEnvironmentPacket");
|
||||||
packetReceiver.registerListener(PacketType::NodeIgnoreRequest, this, "handleNodeIgnoreRequestPacket");
|
|
||||||
packetReceiver.registerListener(PacketType::KillAvatar, this, "handleKillAvatarPacket");
|
|
||||||
packetReceiver.registerListener(PacketType::NodeMuteRequest, this, "handleNodeMuteRequestPacket");
|
packetReceiver.registerListener(PacketType::NodeMuteRequest, this, "handleNodeMuteRequestPacket");
|
||||||
packetReceiver.registerListener(PacketType::RadiusIgnoreRequest, this, "handleRadiusIgnoreRequestPacket");
|
packetReceiver.registerListener(PacketType::KillAvatar, this, "handleKillAvatarPacket");
|
||||||
packetReceiver.registerListener(PacketType::RequestsDomainListData, this, "handleRequestsDomainListDataPacket");
|
|
||||||
packetReceiver.registerListener(PacketType::PerAvatarGainSet, this, "handlePerAvatarGainSetDataPacket");
|
|
||||||
|
|
||||||
connect(nodeList.data(), &NodeList::nodeKilled, this, &AudioMixer::handleNodeKilled);
|
connect(nodeList.data(), &NodeList::nodeKilled, this, &AudioMixer::handleNodeKilled);
|
||||||
}
|
}
|
||||||
|
|
||||||
void AudioMixer::handleAudioPacket(QSharedPointer<ReceivedMessage> message, SharedNodePointer sendingNode) {
|
void AudioMixer::queueAudioPacket(QSharedPointer<ReceivedMessage> message, SharedNodePointer node) {
|
||||||
getOrCreateClientData(sendingNode.data());
|
if (message->getType() == PacketType::SilentAudioFrame) {
|
||||||
DependencyManager::get<NodeList>()->updateNodeWithDataFromPacket(message, sendingNode);
|
|
||||||
}
|
|
||||||
|
|
||||||
void AudioMixer::handleSilentAudioPacket(QSharedPointer<ReceivedMessage> message, SharedNodePointer sendingNode) {
|
|
||||||
_numSilentPackets++;
|
_numSilentPackets++;
|
||||||
getOrCreateClientData(sendingNode.data());
|
}
|
||||||
DependencyManager::get<NodeList>()->updateNodeWithDataFromPacket(message, sendingNode);
|
|
||||||
|
getOrCreateClientData(node.data())->queuePacket(message, node);
|
||||||
}
|
}
|
||||||
|
|
||||||
void AudioMixer::handleMuteEnvironmentPacket(QSharedPointer<ReceivedMessage> message, SharedNodePointer sendingNode) {
|
void AudioMixer::handleMuteEnvironmentPacket(QSharedPointer<ReceivedMessage> message, SharedNodePointer sendingNode) {
|
||||||
|
@ -119,69 +133,28 @@ InputPluginList getInputPlugins() {
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
void saveInputPluginSettings(const InputPluginList& plugins) {
|
// must be here to satisfy a reference in PluginManager::saveSettings()
|
||||||
}
|
void saveInputPluginSettings(const InputPluginList& plugins) {}
|
||||||
|
|
||||||
|
const std::pair<QString, CodecPluginPointer> AudioMixer::negotiateCodec(std::vector<QString> codecs) {
|
||||||
void AudioMixer::handleNegotiateAudioFormat(QSharedPointer<ReceivedMessage> message, SharedNodePointer sendingNode) {
|
|
||||||
QStringList availableCodecs;
|
|
||||||
auto codecPlugins = PluginManager::getInstance()->getCodecPlugins();
|
|
||||||
if (codecPlugins.size() > 0) {
|
|
||||||
for (auto& plugin : codecPlugins) {
|
|
||||||
auto codecName = plugin->getName();
|
|
||||||
qDebug() << "Codec available:" << codecName;
|
|
||||||
availableCodecs.append(codecName);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
qDebug() << "No Codecs available...";
|
|
||||||
}
|
|
||||||
|
|
||||||
CodecPluginPointer selectedCodec;
|
|
||||||
QString selectedCodecName;
|
QString selectedCodecName;
|
||||||
|
CodecPluginPointer selectedCodec;
|
||||||
|
|
||||||
QStringList codecPreferenceList = _codecPreferenceOrder.split(",");
|
// read the codecs requested (by the client)
|
||||||
|
int minPreference = std::numeric_limits<int>::max();
|
||||||
|
for (auto& codec : codecs) {
|
||||||
|
if (_availableCodecs.count(codec) > 0) {
|
||||||
|
int preference = _codecPreferenceOrder.indexOf(codec);
|
||||||
|
|
||||||
// read the codecs requested by the client
|
// choose the preferred, available codec
|
||||||
const int MAX_PREFERENCE = 99999;
|
if (preference >= 0 && preference < minPreference) {
|
||||||
int preferredCodecIndex = MAX_PREFERENCE;
|
minPreference = preference;
|
||||||
QString preferredCodec;
|
selectedCodecName = codec;
|
||||||
quint8 numberOfCodecs = 0;
|
|
||||||
message->readPrimitive(&numberOfCodecs);
|
|
||||||
qDebug() << "numberOfCodecs:" << numberOfCodecs;
|
|
||||||
QStringList codecList;
|
|
||||||
for (quint16 i = 0; i < numberOfCodecs; i++) {
|
|
||||||
QString requestedCodec = message->readString();
|
|
||||||
int preferenceOfThisCodec = codecPreferenceList.indexOf(requestedCodec);
|
|
||||||
bool codecAvailable = availableCodecs.contains(requestedCodec);
|
|
||||||
qDebug() << "requestedCodec:" << requestedCodec << "preference:" << preferenceOfThisCodec << "available:" << codecAvailable;
|
|
||||||
if (codecAvailable) {
|
|
||||||
codecList.append(requestedCodec);
|
|
||||||
if (preferenceOfThisCodec >= 0 && preferenceOfThisCodec < preferredCodecIndex) {
|
|
||||||
qDebug() << "This codec is preferred...";
|
|
||||||
selectedCodecName = requestedCodec;
|
|
||||||
preferredCodecIndex = preferenceOfThisCodec;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
qDebug() << "all requested and available codecs:" << codecList;
|
|
||||||
|
|
||||||
// choose first codec
|
|
||||||
if (!selectedCodecName.isEmpty()) {
|
|
||||||
if (codecPlugins.size() > 0) {
|
|
||||||
for (auto& plugin : codecPlugins) {
|
|
||||||
if (selectedCodecName == plugin->getName()) {
|
|
||||||
qDebug() << "Selecting codec:" << selectedCodecName;
|
|
||||||
selectedCodec = plugin;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
auto clientData = getOrCreateClientData(sendingNode.data());
|
return std::make_pair(selectedCodecName, _availableCodecs[selectedCodecName]);
|
||||||
clientData->setupCodec(selectedCodec, selectedCodecName);
|
|
||||||
qDebug() << "selectedCodecName:" << selectedCodecName;
|
|
||||||
clientData->sendSelectAudioFormat(sendingNode, selectedCodecName);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void AudioMixer::handleNodeKilled(SharedNodePointer killedNode) {
|
void AudioMixer::handleNodeKilled(SharedNodePointer killedNode) {
|
||||||
|
@ -227,42 +200,6 @@ void AudioMixer::handleKillAvatarPacket(QSharedPointer<ReceivedMessage> packet,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void AudioMixer::handleRequestsDomainListDataPacket(QSharedPointer<ReceivedMessage> message, SharedNodePointer senderNode) {
|
|
||||||
auto nodeList = DependencyManager::get<NodeList>();
|
|
||||||
nodeList->getOrCreateLinkedData(senderNode);
|
|
||||||
|
|
||||||
if (senderNode->getLinkedData()) {
|
|
||||||
AudioMixerClientData* nodeData = dynamic_cast<AudioMixerClientData*>(senderNode->getLinkedData());
|
|
||||||
if (nodeData != nullptr) {
|
|
||||||
bool isRequesting;
|
|
||||||
message->readPrimitive(&isRequesting);
|
|
||||||
nodeData->setRequestsDomainListData(isRequesting);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void AudioMixer::handleNodeIgnoreRequestPacket(QSharedPointer<ReceivedMessage> packet, SharedNodePointer sendingNode) {
|
|
||||||
sendingNode->parseIgnoreRequestMessage(packet);
|
|
||||||
}
|
|
||||||
|
|
||||||
void AudioMixer::handlePerAvatarGainSetDataPacket(QSharedPointer<ReceivedMessage> packet, SharedNodePointer sendingNode) {
|
|
||||||
auto clientData = dynamic_cast<AudioMixerClientData*>(sendingNode->getLinkedData());
|
|
||||||
if (clientData) {
|
|
||||||
QUuid listeningNodeUUID = sendingNode->getUUID();
|
|
||||||
// parse the UUID from the packet
|
|
||||||
QUuid audioSourceUUID = QUuid::fromRfc4122(packet->readWithoutCopy(NUM_BYTES_RFC4122_UUID));
|
|
||||||
uint8_t packedGain;
|
|
||||||
packet->readPrimitive(&packedGain);
|
|
||||||
float gain = unpackFloatGainFromByte(packedGain);
|
|
||||||
clientData->hrtfForStream(audioSourceUUID, QUuid()).setGainAdjustment(gain);
|
|
||||||
qDebug() << "Setting gain adjustment for hrtf[" << listeningNodeUUID << "][" << audioSourceUUID << "] to " << gain;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void AudioMixer::handleRadiusIgnoreRequestPacket(QSharedPointer<ReceivedMessage> packet, SharedNodePointer sendingNode) {
|
|
||||||
sendingNode->parseIgnoreRadiusRequestMessage(packet);
|
|
||||||
}
|
|
||||||
|
|
||||||
void AudioMixer::removeHRTFsForFinishedInjector(const QUuid& streamID) {
|
void AudioMixer::removeHRTFsForFinishedInjector(const QUuid& streamID) {
|
||||||
auto injectorClientData = qobject_cast<AudioMixerClientData*>(sender());
|
auto injectorClientData = qobject_cast<AudioMixerClientData*>(sender());
|
||||||
if (injectorClientData) {
|
if (injectorClientData) {
|
||||||
|
@ -323,6 +260,7 @@ void AudioMixer::sendStatsPacket() {
|
||||||
addTiming(_prepareTiming, "prepare");
|
addTiming(_prepareTiming, "prepare");
|
||||||
addTiming(_mixTiming, "mix");
|
addTiming(_mixTiming, "mix");
|
||||||
addTiming(_eventsTiming, "events");
|
addTiming(_eventsTiming, "events");
|
||||||
|
addTiming(_packetsTiming, "packets");
|
||||||
|
|
||||||
#ifdef HIFI_AUDIO_MIXER_DEBUG
|
#ifdef HIFI_AUDIO_MIXER_DEBUG
|
||||||
timingStats["ns_per_mix"] = (_stats.totalMixes > 0) ? (float)(_stats.mixTime / _stats.totalMixes) : 0;
|
timingStats["ns_per_mix"] = (_stats.totalMixes > 0) ? (float)(_stats.mixTime / _stats.totalMixes) : 0;
|
||||||
|
@ -452,20 +390,28 @@ void AudioMixer::start() {
|
||||||
++frame;
|
++frame;
|
||||||
++_numStatFrames;
|
++_numStatFrames;
|
||||||
|
|
||||||
// play nice with qt event-looping
|
// process queued events (networking, global audio packets, &c.)
|
||||||
{
|
{
|
||||||
auto eventsTimer = _eventsTiming.timer();
|
auto eventsTimer = _eventsTiming.timer();
|
||||||
|
|
||||||
// since we're a while loop we need to yield to qt's event processing
|
// since we're a while loop we need to yield to qt's event processing
|
||||||
QCoreApplication::processEvents();
|
QCoreApplication::processEvents();
|
||||||
|
|
||||||
|
// process (node-isolated) audio packets across slave threads
|
||||||
|
{
|
||||||
|
nodeList->nestedEach([&](NodeList::const_iterator cbegin, NodeList::const_iterator cend) {
|
||||||
|
auto packetsTimer = _packetsTiming.timer();
|
||||||
|
_slavePool.processPackets(cbegin, cend);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (_isFinished) {
|
if (_isFinished) {
|
||||||
// alert qt eventing that this is finished
|
// alert qt eventing that this is finished
|
||||||
QCoreApplication::sendPostedEvents(this, QEvent::DeferredDelete);
|
QCoreApplication::sendPostedEvents(this, QEvent::DeferredDelete);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
std::chrono::microseconds AudioMixer::timeFrame(p_high_resolution_clock::time_point& timestamp) {
|
std::chrono::microseconds AudioMixer::timeFrame(p_high_resolution_clock::time_point& timestamp) {
|
||||||
|
@ -629,7 +575,8 @@ void AudioMixer::parseSettingsObject(const QJsonObject &settingsObject) {
|
||||||
|
|
||||||
const QString CODEC_PREFERENCE_ORDER = "codec_preference_order";
|
const QString CODEC_PREFERENCE_ORDER = "codec_preference_order";
|
||||||
if (audioEnvGroupObject[CODEC_PREFERENCE_ORDER].isString()) {
|
if (audioEnvGroupObject[CODEC_PREFERENCE_ORDER].isString()) {
|
||||||
_codecPreferenceOrder = audioEnvGroupObject[CODEC_PREFERENCE_ORDER].toString();
|
QString codecPreferenceOrder = audioEnvGroupObject[CODEC_PREFERENCE_ORDER].toString();
|
||||||
|
_codecPreferenceOrder = codecPreferenceOrder.split(",");
|
||||||
qDebug() << "Codec preference order changed to" << _codecPreferenceOrder;
|
qDebug() << "Codec preference order changed to" << _codecPreferenceOrder;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -49,6 +49,7 @@ public:
|
||||||
static const QHash<QString, AABox>& getAudioZones() { return _audioZones; }
|
static const QHash<QString, AABox>& getAudioZones() { return _audioZones; }
|
||||||
static const QVector<ZoneSettings>& getZoneSettings() { return _zoneSettings; }
|
static const QVector<ZoneSettings>& getZoneSettings() { return _zoneSettings; }
|
||||||
static const QVector<ReverbSettings>& getReverbSettings() { return _zoneReverbSettings; }
|
static const QVector<ReverbSettings>& getReverbSettings() { return _zoneReverbSettings; }
|
||||||
|
static const std::pair<QString, CodecPluginPointer> negotiateCodec(std::vector<QString> codecs);
|
||||||
|
|
||||||
public slots:
|
public slots:
|
||||||
void run() override;
|
void run() override;
|
||||||
|
@ -56,20 +57,14 @@ public slots:
|
||||||
|
|
||||||
private slots:
|
private slots:
|
||||||
// packet handlers
|
// packet handlers
|
||||||
void handleAudioPacket(QSharedPointer<ReceivedMessage> packet, SharedNodePointer sendingNode);
|
|
||||||
void handleSilentAudioPacket(QSharedPointer<ReceivedMessage> packet, SharedNodePointer sendingNode);
|
|
||||||
void handleMuteEnvironmentPacket(QSharedPointer<ReceivedMessage> packet, SharedNodePointer sendingNode);
|
void handleMuteEnvironmentPacket(QSharedPointer<ReceivedMessage> packet, SharedNodePointer sendingNode);
|
||||||
void handleNegotiateAudioFormat(QSharedPointer<ReceivedMessage> message, SharedNodePointer sendingNode);
|
|
||||||
void handleNodeKilled(SharedNodePointer killedNode);
|
|
||||||
void handleRequestsDomainListDataPacket(QSharedPointer<ReceivedMessage> message, SharedNodePointer senderNode);
|
|
||||||
void handleNodeIgnoreRequestPacket(QSharedPointer<ReceivedMessage> packet, SharedNodePointer sendingNode);
|
|
||||||
void handleRadiusIgnoreRequestPacket(QSharedPointer<ReceivedMessage> packet, SharedNodePointer sendingNode);
|
|
||||||
void handleKillAvatarPacket(QSharedPointer<ReceivedMessage> packet, SharedNodePointer sendingNode);
|
|
||||||
void handleNodeMuteRequestPacket(QSharedPointer<ReceivedMessage> packet, SharedNodePointer sendingNode);
|
void handleNodeMuteRequestPacket(QSharedPointer<ReceivedMessage> packet, SharedNodePointer sendingNode);
|
||||||
void handlePerAvatarGainSetDataPacket(QSharedPointer<ReceivedMessage> packet, SharedNodePointer sendingNode);
|
void handleNodeKilled(SharedNodePointer killedNode);
|
||||||
|
void handleKillAvatarPacket(QSharedPointer<ReceivedMessage> packet, SharedNodePointer sendingNode);
|
||||||
|
|
||||||
void start();
|
void queueAudioPacket(QSharedPointer<ReceivedMessage> packet, SharedNodePointer sendingNode);
|
||||||
void removeHRTFsForFinishedInjector(const QUuid& streamID);
|
void removeHRTFsForFinishedInjector(const QUuid& streamID);
|
||||||
|
void start();
|
||||||
|
|
||||||
private:
|
private:
|
||||||
// mixing helpers
|
// mixing helpers
|
||||||
|
@ -93,8 +88,6 @@ private:
|
||||||
int _numStatFrames { 0 };
|
int _numStatFrames { 0 };
|
||||||
AudioMixerStats _stats;
|
AudioMixerStats _stats;
|
||||||
|
|
||||||
QString _codecPreferenceOrder;
|
|
||||||
|
|
||||||
AudioMixerSlavePool _slavePool;
|
AudioMixerSlavePool _slavePool;
|
||||||
|
|
||||||
class Timer {
|
class Timer {
|
||||||
|
@ -124,13 +117,17 @@ private:
|
||||||
Timer _prepareTiming;
|
Timer _prepareTiming;
|
||||||
Timer _mixTiming;
|
Timer _mixTiming;
|
||||||
Timer _eventsTiming;
|
Timer _eventsTiming;
|
||||||
|
Timer _packetsTiming;
|
||||||
|
|
||||||
static int _numStaticJitterFrames; // -1 denotes dynamic jitter buffering
|
static int _numStaticJitterFrames; // -1 denotes dynamic jitter buffering
|
||||||
static float _noiseMutingThreshold;
|
static float _noiseMutingThreshold;
|
||||||
static float _attenuationPerDoublingInDistance;
|
static float _attenuationPerDoublingInDistance;
|
||||||
|
static std::map<QString, CodecPluginPointer> _availableCodecs;
|
||||||
|
static QStringList _codecPreferenceOrder;
|
||||||
static QHash<QString, AABox> _audioZones;
|
static QHash<QString, AABox> _audioZones;
|
||||||
static QVector<ZoneSettings> _zoneSettings;
|
static QVector<ZoneSettings> _zoneSettings;
|
||||||
static QVector<ReverbSettings> _zoneReverbSettings;
|
static QVector<ReverbSettings> _zoneReverbSettings;
|
||||||
|
|
||||||
};
|
};
|
||||||
|
|
||||||
#endif // hifi_AudioMixer_h
|
#endif // hifi_AudioMixer_h
|
||||||
|
|
|
@ -19,6 +19,7 @@
|
||||||
|
|
||||||
#include "InjectedAudioStream.h"
|
#include "InjectedAudioStream.h"
|
||||||
|
|
||||||
|
#include "AudioHelpers.h"
|
||||||
#include "AudioMixer.h"
|
#include "AudioMixer.h"
|
||||||
#include "AudioMixerClientData.h"
|
#include "AudioMixerClientData.h"
|
||||||
|
|
||||||
|
@ -47,6 +48,92 @@ AudioMixerClientData::~AudioMixerClientData() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void AudioMixerClientData::queuePacket(QSharedPointer<ReceivedMessage> message, SharedNodePointer node) {
|
||||||
|
if (!_packetQueue.node) {
|
||||||
|
_packetQueue.node = node;
|
||||||
|
}
|
||||||
|
_packetQueue.push(message);
|
||||||
|
}
|
||||||
|
|
||||||
|
void AudioMixerClientData::processPackets() {
|
||||||
|
SharedNodePointer node = _packetQueue.node;
|
||||||
|
assert(_packetQueue.empty() || node);
|
||||||
|
_packetQueue.node.clear();
|
||||||
|
|
||||||
|
while (!_packetQueue.empty()) {
|
||||||
|
auto& packet = _packetQueue.back();
|
||||||
|
|
||||||
|
switch (packet->getType()) {
|
||||||
|
case PacketType::MicrophoneAudioNoEcho:
|
||||||
|
case PacketType::MicrophoneAudioWithEcho:
|
||||||
|
case PacketType::InjectAudio:
|
||||||
|
case PacketType::AudioStreamStats:
|
||||||
|
case PacketType::SilentAudioFrame: {
|
||||||
|
QMutexLocker lock(&getMutex());
|
||||||
|
parseData(*packet);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case PacketType::NegotiateAudioFormat:
|
||||||
|
negotiateAudioFormat(*packet, node);
|
||||||
|
break;
|
||||||
|
case PacketType::RequestsDomainListData:
|
||||||
|
parseRequestsDomainListData(*packet);
|
||||||
|
break;
|
||||||
|
case PacketType::PerAvatarGainSet:
|
||||||
|
parsePerAvatarGainSet(*packet, node);
|
||||||
|
break;
|
||||||
|
case PacketType::NodeIgnoreRequest:
|
||||||
|
parseNodeIgnoreRequest(packet, node);
|
||||||
|
break;
|
||||||
|
case PacketType::RadiusIgnoreRequest:
|
||||||
|
parseRadiusIgnoreRequest(packet, node);
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
Q_UNREACHABLE();
|
||||||
|
}
|
||||||
|
|
||||||
|
_packetQueue.pop();
|
||||||
|
}
|
||||||
|
assert(_packetQueue.empty());
|
||||||
|
}
|
||||||
|
|
||||||
|
void AudioMixerClientData::negotiateAudioFormat(ReceivedMessage& message, const SharedNodePointer& node) {
|
||||||
|
quint8 numberOfCodecs;
|
||||||
|
message.readPrimitive(&numberOfCodecs);
|
||||||
|
std::vector<QString> codecs;
|
||||||
|
for (auto i = 0; i < numberOfCodecs; i++) {
|
||||||
|
codecs.push_back(message.readString());
|
||||||
|
}
|
||||||
|
const std::pair<QString, CodecPluginPointer> codec = AudioMixer::negotiateCodec(codecs);
|
||||||
|
|
||||||
|
setupCodec(codec.second, codec.first);
|
||||||
|
sendSelectAudioFormat(node, codec.first);
|
||||||
|
}
|
||||||
|
|
||||||
|
void AudioMixerClientData::parseRequestsDomainListData(ReceivedMessage& message) {
|
||||||
|
bool isRequesting;
|
||||||
|
message.readPrimitive(&isRequesting);
|
||||||
|
setRequestsDomainListData(isRequesting);
|
||||||
|
}
|
||||||
|
|
||||||
|
void AudioMixerClientData::parsePerAvatarGainSet(ReceivedMessage& message, const SharedNodePointer& node) {
|
||||||
|
QUuid uuid = node->getUUID();
|
||||||
|
// parse the UUID from the packet
|
||||||
|
QUuid avatarUuid = QUuid::fromRfc4122(message.readWithoutCopy(NUM_BYTES_RFC4122_UUID));
|
||||||
|
uint8_t packedGain;
|
||||||
|
message.readPrimitive(&packedGain);
|
||||||
|
float gain = unpackFloatGainFromByte(packedGain);
|
||||||
|
hrtfForStream(avatarUuid, QUuid()).setGainAdjustment(gain);
|
||||||
|
qDebug() << "Setting gain adjustment for hrtf[" << uuid << "][" << avatarUuid << "] to " << gain;
|
||||||
|
}
|
||||||
|
|
||||||
|
void AudioMixerClientData::parseNodeIgnoreRequest(QSharedPointer<ReceivedMessage> message, const SharedNodePointer& node) {
|
||||||
|
node->parseIgnoreRequestMessage(message);
|
||||||
|
}
|
||||||
|
|
||||||
|
void AudioMixerClientData::parseRadiusIgnoreRequest(QSharedPointer<ReceivedMessage> message, const SharedNodePointer& node) {
|
||||||
|
node->parseIgnoreRadiusRequestMessage(message);
|
||||||
|
}
|
||||||
|
|
||||||
AvatarAudioStream* AudioMixerClientData::getAvatarAudioStream() {
|
AvatarAudioStream* AudioMixerClientData::getAvatarAudioStream() {
|
||||||
QReadLocker readLocker { &_streamsLock };
|
QReadLocker readLocker { &_streamsLock };
|
||||||
|
|
|
@ -12,6 +12,8 @@
|
||||||
#ifndef hifi_AudioMixerClientData_h
|
#ifndef hifi_AudioMixerClientData_h
|
||||||
#define hifi_AudioMixerClientData_h
|
#define hifi_AudioMixerClientData_h
|
||||||
|
|
||||||
|
#include <queue>
|
||||||
|
|
||||||
#include <QtCore/QJsonObject>
|
#include <QtCore/QJsonObject>
|
||||||
|
|
||||||
#include <AABox.h>
|
#include <AABox.h>
|
||||||
|
@ -34,6 +36,9 @@ public:
|
||||||
using SharedStreamPointer = std::shared_ptr<PositionalAudioStream>;
|
using SharedStreamPointer = std::shared_ptr<PositionalAudioStream>;
|
||||||
using AudioStreamMap = std::unordered_map<QUuid, SharedStreamPointer>;
|
using AudioStreamMap = std::unordered_map<QUuid, SharedStreamPointer>;
|
||||||
|
|
||||||
|
void queuePacket(QSharedPointer<ReceivedMessage> packet, SharedNodePointer node);
|
||||||
|
void processPackets();
|
||||||
|
|
||||||
// locks the mutex to make a copy
|
// locks the mutex to make a copy
|
||||||
AudioStreamMap getAudioStreams() { QReadLocker readLock { &_streamsLock }; return _audioStreams; }
|
AudioStreamMap getAudioStreams() { QReadLocker readLock { &_streamsLock }; return _audioStreams; }
|
||||||
AvatarAudioStream* getAvatarAudioStream();
|
AvatarAudioStream* getAvatarAudioStream();
|
||||||
|
@ -56,7 +61,13 @@ public:
|
||||||
|
|
||||||
void removeAgentAvatarAudioStream();
|
void removeAgentAvatarAudioStream();
|
||||||
|
|
||||||
|
// packet parsers
|
||||||
int parseData(ReceivedMessage& message) override;
|
int parseData(ReceivedMessage& message) override;
|
||||||
|
void negotiateAudioFormat(ReceivedMessage& message, const SharedNodePointer& node);
|
||||||
|
void parseRequestsDomainListData(ReceivedMessage& message);
|
||||||
|
void parsePerAvatarGainSet(ReceivedMessage& message, const SharedNodePointer& node);
|
||||||
|
void parseNodeIgnoreRequest(QSharedPointer<ReceivedMessage> message, const SharedNodePointer& node);
|
||||||
|
void parseRadiusIgnoreRequest(QSharedPointer<ReceivedMessage> message, const SharedNodePointer& node);
|
||||||
|
|
||||||
// attempt to pop a frame from each audio stream, and return the number of streams from this client
|
// attempt to pop a frame from each audio stream, and return the number of streams from this client
|
||||||
int checkBuffersBeforeFrameSend();
|
int checkBuffersBeforeFrameSend();
|
||||||
|
@ -105,11 +116,15 @@ public slots:
|
||||||
void sendSelectAudioFormat(SharedNodePointer node, const QString& selectedCodecName);
|
void sendSelectAudioFormat(SharedNodePointer node, const QString& selectedCodecName);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
using IgnoreZone = AABox;
|
struct PacketQueue : public std::queue<QSharedPointer<ReceivedMessage>> {
|
||||||
|
QWeakPointer<Node> node;
|
||||||
|
};
|
||||||
|
PacketQueue _packetQueue;
|
||||||
|
|
||||||
QReadWriteLock _streamsLock;
|
QReadWriteLock _streamsLock;
|
||||||
AudioStreamMap _audioStreams; // microphone stream from avatar is stored under key of null UUID
|
AudioStreamMap _audioStreams; // microphone stream from avatar is stored under key of null UUID
|
||||||
|
|
||||||
|
using IgnoreZone = AABox;
|
||||||
class IgnoreZoneMemo {
|
class IgnoreZoneMemo {
|
||||||
public:
|
public:
|
||||||
IgnoreZoneMemo(AudioMixerClientData& data) : _data(data) {}
|
IgnoreZoneMemo(AudioMixerClientData& data) : _data(data) {}
|
||||||
|
|
|
@ -53,7 +53,14 @@ inline float computeGain(const AvatarAudioStream& listeningNodeStream, const Pos
|
||||||
inline float computeAzimuth(const AvatarAudioStream& listeningNodeStream, const PositionalAudioStream& streamToAdd,
|
inline float computeAzimuth(const AvatarAudioStream& listeningNodeStream, const PositionalAudioStream& streamToAdd,
|
||||||
const glm::vec3& relativePosition);
|
const glm::vec3& relativePosition);
|
||||||
|
|
||||||
void AudioMixerSlave::configure(ConstIter begin, ConstIter end, unsigned int frame, float throttlingRatio) {
|
void AudioMixerSlave::processPackets(const SharedNodePointer& node) {
|
||||||
|
AudioMixerClientData* data = (AudioMixerClientData*)node->getLinkedData();
|
||||||
|
if (data) {
|
||||||
|
data->processPackets();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void AudioMixerSlave::configureMix(ConstIter begin, ConstIter end, unsigned int frame, float throttlingRatio) {
|
||||||
_begin = begin;
|
_begin = begin;
|
||||||
_end = end;
|
_end = end;
|
||||||
_frame = frame;
|
_frame = frame;
|
||||||
|
|
|
@ -30,9 +30,13 @@ class AudioMixerSlave {
|
||||||
public:
|
public:
|
||||||
using ConstIter = NodeList::const_iterator;
|
using ConstIter = NodeList::const_iterator;
|
||||||
|
|
||||||
void configure(ConstIter begin, ConstIter end, unsigned int frame, float throttlingRatio);
|
// process packets for a given node (requires no configuration)
|
||||||
|
void processPackets(const SharedNodePointer& node);
|
||||||
|
|
||||||
// mix and broadcast non-ignored streams to the node
|
// configure a round of mixing
|
||||||
|
void configureMix(ConstIter begin, ConstIter end, unsigned int frame, float throttlingRatio);
|
||||||
|
|
||||||
|
// mix and broadcast non-ignored streams to the node (requires configuration using configureMix, above)
|
||||||
// returns true if a mixed packet was sent to the node
|
// returns true if a mixed packet was sent to the node
|
||||||
void mix(const SharedNodePointer& node);
|
void mix(const SharedNodePointer& node);
|
||||||
|
|
||||||
|
|
|
@ -21,7 +21,7 @@ void AudioMixerSlaveThread::run() {
|
||||||
// iterate over all available nodes
|
// iterate over all available nodes
|
||||||
SharedNodePointer node;
|
SharedNodePointer node;
|
||||||
while (try_pop(node)) {
|
while (try_pop(node)) {
|
||||||
mix(node);
|
(this->*_function)(node);
|
||||||
}
|
}
|
||||||
|
|
||||||
bool stopping = _stop;
|
bool stopping = _stop;
|
||||||
|
@ -41,7 +41,11 @@ void AudioMixerSlaveThread::wait() {
|
||||||
});
|
});
|
||||||
++_pool._numStarted;
|
++_pool._numStarted;
|
||||||
}
|
}
|
||||||
configure(_pool._begin, _pool._end, _pool._frame, _pool._throttlingRatio);
|
|
||||||
|
if (_pool._configure) {
|
||||||
|
_pool._configure(*this);
|
||||||
|
}
|
||||||
|
_function = _pool._function;
|
||||||
}
|
}
|
||||||
|
|
||||||
void AudioMixerSlaveThread::notify(bool stopping) {
|
void AudioMixerSlaveThread::notify(bool stopping) {
|
||||||
|
@ -64,16 +68,31 @@ bool AudioMixerSlaveThread::try_pop(SharedNodePointer& node) {
|
||||||
static AudioMixerSlave slave;
|
static AudioMixerSlave slave;
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
void AudioMixerSlavePool::processPackets(ConstIter begin, ConstIter end) {
|
||||||
|
_function = &AudioMixerSlave::processPackets;
|
||||||
|
_configure = [](AudioMixerSlave& slave) {};
|
||||||
|
run(begin, end);
|
||||||
|
}
|
||||||
|
|
||||||
void AudioMixerSlavePool::mix(ConstIter begin, ConstIter end, unsigned int frame, float throttlingRatio) {
|
void AudioMixerSlavePool::mix(ConstIter begin, ConstIter end, unsigned int frame, float throttlingRatio) {
|
||||||
_begin = begin;
|
_function = &AudioMixerSlave::mix;
|
||||||
_end = end;
|
_configure = [&](AudioMixerSlave& slave) {
|
||||||
|
slave.configureMix(_begin, _end, _frame, _throttlingRatio);
|
||||||
|
};
|
||||||
_frame = frame;
|
_frame = frame;
|
||||||
_throttlingRatio = throttlingRatio;
|
_throttlingRatio = throttlingRatio;
|
||||||
|
|
||||||
|
run(begin, end);
|
||||||
|
}
|
||||||
|
|
||||||
|
void AudioMixerSlavePool::run(ConstIter begin, ConstIter end) {
|
||||||
|
_begin = begin;
|
||||||
|
_end = end;
|
||||||
|
|
||||||
#ifdef AUDIO_SINGLE_THREADED
|
#ifdef AUDIO_SINGLE_THREADED
|
||||||
slave.configure(_begin, _end, frame, throttlingRatio);
|
_configure(slave);
|
||||||
std::for_each(begin, end, [&](const SharedNodePointer& node) {
|
std::for_each(begin, end, [&](const SharedNodePointer& node) {
|
||||||
slave.mix(node);
|
_function(slave, node);
|
||||||
});
|
});
|
||||||
#else
|
#else
|
||||||
// fill the queue
|
// fill the queue
|
||||||
|
@ -84,7 +103,7 @@ void AudioMixerSlavePool::mix(ConstIter begin, ConstIter end, unsigned int frame
|
||||||
{
|
{
|
||||||
Lock lock(_mutex);
|
Lock lock(_mutex);
|
||||||
|
|
||||||
// mix
|
// run
|
||||||
_numStarted = _numFinished = 0;
|
_numStarted = _numFinished = 0;
|
||||||
_slaveCondition.notify_all();
|
_slaveCondition.notify_all();
|
||||||
|
|
||||||
|
|
|
@ -43,6 +43,7 @@ private:
|
||||||
bool try_pop(SharedNodePointer& node);
|
bool try_pop(SharedNodePointer& node);
|
||||||
|
|
||||||
AudioMixerSlavePool& _pool;
|
AudioMixerSlavePool& _pool;
|
||||||
|
void (AudioMixerSlave::*_function)(const SharedNodePointer& node) { nullptr };
|
||||||
bool _stop { false };
|
bool _stop { false };
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -60,6 +61,9 @@ public:
|
||||||
AudioMixerSlavePool(int numThreads = QThread::idealThreadCount()) { setNumThreads(numThreads); }
|
AudioMixerSlavePool(int numThreads = QThread::idealThreadCount()) { setNumThreads(numThreads); }
|
||||||
~AudioMixerSlavePool() { resize(0); }
|
~AudioMixerSlavePool() { resize(0); }
|
||||||
|
|
||||||
|
// process packets on slave threads
|
||||||
|
void processPackets(ConstIter begin, ConstIter end);
|
||||||
|
|
||||||
// mix on slave threads
|
// mix on slave threads
|
||||||
void mix(ConstIter begin, ConstIter end, unsigned int frame, float throttlingRatio);
|
void mix(ConstIter begin, ConstIter end, unsigned int frame, float throttlingRatio);
|
||||||
|
|
||||||
|
@ -70,6 +74,7 @@ public:
|
||||||
int numThreads() { return _numThreads; }
|
int numThreads() { return _numThreads; }
|
||||||
|
|
||||||
private:
|
private:
|
||||||
|
void run(ConstIter begin, ConstIter end);
|
||||||
void resize(int numThreads);
|
void resize(int numThreads);
|
||||||
|
|
||||||
std::vector<std::unique_ptr<AudioMixerSlaveThread>> _slaves;
|
std::vector<std::unique_ptr<AudioMixerSlaveThread>> _slaves;
|
||||||
|
@ -82,6 +87,8 @@ private:
|
||||||
Mutex _mutex;
|
Mutex _mutex;
|
||||||
ConditionVariable _slaveCondition;
|
ConditionVariable _slaveCondition;
|
||||||
ConditionVariable _poolCondition;
|
ConditionVariable _poolCondition;
|
||||||
|
void (AudioMixerSlave::*_function)(const SharedNodePointer& node);
|
||||||
|
std::function<void(AudioMixerSlave&)> _configure;
|
||||||
int _numThreads { 0 };
|
int _numThreads { 0 };
|
||||||
int _numStarted { 0 }; // guarded by _mutex
|
int _numStarted { 0 }; // guarded by _mutex
|
||||||
int _numFinished { 0 }; // guarded by _mutex
|
int _numFinished { 0 }; // guarded by _mutex
|
||||||
|
|
|
@ -717,14 +717,20 @@ SharedNodePointer LimitedNodeList::soloNodeOfType(NodeType_t nodeType) {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
void LimitedNodeList::getPacketStats(float& packetsPerSecond, float& bytesPerSecond) {
|
void LimitedNodeList::getPacketStats(float& packetsInPerSecond, float& bytesInPerSecond, float& packetsOutPerSecond, float& bytesOutPerSecond) {
|
||||||
packetsPerSecond = (float) _numCollectedPackets / ((float) _packetStatTimer.elapsed() / 1000.0f);
|
packetsInPerSecond = (float) getPacketReceiver().getInPacketCount() / ((float) _packetStatTimer.elapsed() / 1000.0f);
|
||||||
bytesPerSecond = (float) _numCollectedBytes / ((float) _packetStatTimer.elapsed() / 1000.0f);
|
bytesInPerSecond = (float) getPacketReceiver().getInByteCount() / ((float) _packetStatTimer.elapsed() / 1000.0f);
|
||||||
|
|
||||||
|
packetsOutPerSecond = (float) _numCollectedPackets / ((float) _packetStatTimer.elapsed() / 1000.0f);
|
||||||
|
bytesOutPerSecond = (float) _numCollectedBytes / ((float) _packetStatTimer.elapsed() / 1000.0f);
|
||||||
}
|
}
|
||||||
|
|
||||||
void LimitedNodeList::resetPacketStats() {
|
void LimitedNodeList::resetPacketStats() {
|
||||||
|
getPacketReceiver().resetCounters();
|
||||||
|
|
||||||
_numCollectedPackets = 0;
|
_numCollectedPackets = 0;
|
||||||
_numCollectedBytes = 0;
|
_numCollectedBytes = 0;
|
||||||
|
|
||||||
_packetStatTimer.restart();
|
_packetStatTimer.restart();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -161,7 +161,7 @@ public:
|
||||||
unsigned int broadcastToNodes(std::unique_ptr<NLPacket> packet, const NodeSet& destinationNodeTypes);
|
unsigned int broadcastToNodes(std::unique_ptr<NLPacket> packet, const NodeSet& destinationNodeTypes);
|
||||||
SharedNodePointer soloNodeOfType(NodeType_t nodeType);
|
SharedNodePointer soloNodeOfType(NodeType_t nodeType);
|
||||||
|
|
||||||
void getPacketStats(float &packetsPerSecond, float &bytesPerSecond);
|
void getPacketStats(float& packetsInPerSecond, float& bytesInPerSecond, float& packetsOutPerSecond, float& bytesOutPerSecond);
|
||||||
void resetPacketStats();
|
void resetPacketStats();
|
||||||
|
|
||||||
std::unique_ptr<NLPacket> constructPingPacket(PingType_t pingType = PingType::Agnostic);
|
std::unique_ptr<NLPacket> constructPingPacket(PingType_t pingType = PingType::Agnostic);
|
||||||
|
|
|
@ -92,12 +92,17 @@ void ThreadedAssignment::commonInit(const QString& targetName, NodeType_t nodeTy
|
||||||
void ThreadedAssignment::addPacketStatsAndSendStatsPacket(QJsonObject statsObject) {
|
void ThreadedAssignment::addPacketStatsAndSendStatsPacket(QJsonObject statsObject) {
|
||||||
auto nodeList = DependencyManager::get<NodeList>();
|
auto nodeList = DependencyManager::get<NodeList>();
|
||||||
|
|
||||||
float packetsPerSecond, bytesPerSecond;
|
float packetsInPerSecond, bytesInPerSecond, packetsOutPerSecond, bytesOutPerSecond;
|
||||||
nodeList->getPacketStats(packetsPerSecond, bytesPerSecond);
|
nodeList->getPacketStats(packetsInPerSecond, bytesInPerSecond, packetsOutPerSecond, bytesOutPerSecond);
|
||||||
nodeList->resetPacketStats();
|
nodeList->resetPacketStats();
|
||||||
|
|
||||||
statsObject["packets_per_second"] = packetsPerSecond;
|
QJsonObject ioStats;
|
||||||
statsObject["bytes_per_second"] = bytesPerSecond;
|
ioStats["inbound_bytes_per_s"] = bytesInPerSecond;
|
||||||
|
ioStats["inbound_packets_per_s"] = packetsInPerSecond;
|
||||||
|
ioStats["outbound_bytes_per_s"] = bytesOutPerSecond;
|
||||||
|
ioStats["outbound_packets_per_s"] = packetsOutPerSecond;
|
||||||
|
|
||||||
|
statsObject["io_stats"] = ioStats;
|
||||||
|
|
||||||
nodeList->sendStatsToDomainServer(statsObject);
|
nodeList->sendStatsToDomainServer(statsObject);
|
||||||
}
|
}
|
||||||
|
|
|
@ -123,8 +123,6 @@ const CodecPluginList& PluginManager::getCodecPlugins() {
|
||||||
static CodecPluginList codecPlugins;
|
static CodecPluginList codecPlugins;
|
||||||
static std::once_flag once;
|
static std::once_flag once;
|
||||||
std::call_once(once, [&] {
|
std::call_once(once, [&] {
|
||||||
//codecPlugins = ::getCodecPlugins();
|
|
||||||
|
|
||||||
// Now grab the dynamic plugins
|
// Now grab the dynamic plugins
|
||||||
for (auto loader : getLoadedPlugins()) {
|
for (auto loader : getLoadedPlugins()) {
|
||||||
CodecProvider* codecProvider = qobject_cast<CodecProvider*>(loader->instance());
|
CodecProvider* codecProvider = qobject_cast<CodecProvider*>(loader->instance());
|
||||||
|
|
|
@ -145,10 +145,8 @@ void Deck::processFrames() {
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!nextClip) {
|
if (!nextClip) {
|
||||||
qCDebug(recordingLog) << "No more frames available";
|
|
||||||
// No more frames available, so handle the end of playback
|
// No more frames available, so handle the end of playback
|
||||||
if (_loop) {
|
if (_loop) {
|
||||||
qCDebug(recordingLog) << "Looping enabled, seeking back to beginning";
|
|
||||||
// If we have looping enabled, start the playback over
|
// If we have looping enabled, start the playback over
|
||||||
seek(0);
|
seek(0);
|
||||||
// FIXME configure the recording scripting interface to reset the avatar basis on a loop
|
// FIXME configure the recording scripting interface to reset the avatar basis on a loop
|
||||||
|
|
|
@ -24,19 +24,18 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
.top-bar {
|
.top-bar {
|
||||||
width: 100%;
|
|
||||||
height: 90px;
|
height: 90px;
|
||||||
background: linear-gradient(#2b2b2b, #1e1e1e);
|
background: linear-gradient(#2b2b2b, #1e1e1e);
|
||||||
font-weight: bold;
|
font-weight: bold;
|
||||||
}
|
padding-left: 30px;
|
||||||
|
padding-right: 30px;
|
||||||
.top-bar .myContainer {
|
|
||||||
display: flex;
|
display: flex;
|
||||||
justify-content: space-between;
|
|
||||||
align-items: center;
|
align-items: center;
|
||||||
margin-left: 30px;
|
position: fixed;
|
||||||
margin-right: 30px;
|
width: 480px;
|
||||||
height: 100%;
|
top: 0;
|
||||||
|
z-index: 1;
|
||||||
|
justify-content: space-between;
|
||||||
}
|
}
|
||||||
|
|
||||||
#refresh-button {
|
#refresh-button {
|
||||||
|
@ -46,6 +45,7 @@
|
||||||
|
|
||||||
.main {
|
.main {
|
||||||
padding: 30px;
|
padding: 30px;
|
||||||
|
margin-top: 90px;
|
||||||
}
|
}
|
||||||
|
|
||||||
#user-info-div {
|
#user-info-div {
|
||||||
|
@ -219,7 +219,11 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
.dropdown-menu {
|
.dropdown-menu {
|
||||||
width: 280px;
|
width: 350px;
|
||||||
|
border: 0;
|
||||||
|
display: block;
|
||||||
|
float: none;
|
||||||
|
position: inherit;
|
||||||
}
|
}
|
||||||
|
|
||||||
.dropdown-menu li {
|
.dropdown-menu li {
|
||||||
|
@ -231,52 +235,32 @@
|
||||||
padding: 0px;
|
padding: 0px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.dropdown-menu li:hover {
|
.dropdown-menu li.current {
|
||||||
background: #dcdcdc;
|
background: #dcdcdc;
|
||||||
}
|
}
|
||||||
|
|
||||||
.dropdown-menu li h6 {
|
.dropdown-menu li h5 {
|
||||||
font-weight: bold;
|
font-weight: bold;
|
||||||
}
|
}
|
||||||
|
|
||||||
.dropdown-menu li p {
|
.dropdown-menu li p {
|
||||||
font-size: 11px;
|
font-size: 14px;
|
||||||
}
|
}
|
||||||
|
|
||||||
</style>
|
</style>
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<div class="top-bar">
|
<div class="top-bar">
|
||||||
<div class="myContainer">
|
|
||||||
<div>Users Online</div>
|
<div>Users Online</div>
|
||||||
<img id="refresh-button" onclick="pollUsers()" src="https://hifi-content.s3.amazonaws.com/faye/tablet-dev/refresh-icon.svg"></img>
|
<img id="refresh-button" onclick="pollUsers()" src="https://hifi-content.s3.amazonaws.com/faye/tablet-dev/refresh-icon.svg"></img>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
|
||||||
<div class="main">
|
<div class="main">
|
||||||
<div id="user-info-div">
|
<div id="user-info-div">
|
||||||
<h4></h4>
|
<h4></h4>
|
||||||
<div class="dropdown">
|
<button id="visibility-toggle" data-toggle="modal" data-target="#myModal2" aria-haspopup="true" aria-expanded="false">
|
||||||
<button id="visibility-toggle" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false">
|
|
||||||
Online
|
Online
|
||||||
<span class="glyphicon glyphicon-menu-down"></span>
|
<span class="glyphicon glyphicon-menu-down"></span>
|
||||||
</button>
|
</button>
|
||||||
<ul class="dropdown-menu" aria-labelledby="visibility-toggle">
|
|
||||||
<li class="visibility-option" data-visibility="all">
|
|
||||||
<h6>Online</h6>
|
|
||||||
<p>You will be shown online to everyone else. Anybody will be able to find you from the users online list and jump to your current location.</p>
|
|
||||||
</li>
|
|
||||||
<li role="separator" class="divider"></li>
|
|
||||||
<li class="visibility-option" data-visibility="friends">
|
|
||||||
<h6>Available to Friends Only</h6>
|
|
||||||
<p>You will be shown online only to users you have added as friends. Other users may still interact with you in the same domain, but they won't be able to find you from the users online list.</p>
|
|
||||||
</li>
|
|
||||||
<li role="separator" class="divider"></li>
|
|
||||||
<li class="visibility-option" data-visibility="none">
|
|
||||||
<h6>Appear Offline</h6>
|
|
||||||
<p>No one will be able to find you from the users online list. However, this does not prevent other users in the same domain from interacting with you. For a complete "Do not disturb" mode, you may want to go to your own private domain and set allow entering to no one.</p>
|
|
||||||
</li>
|
|
||||||
</ul>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
<ul class="tabs">
|
<ul class="tabs">
|
||||||
<li tab-id="tab-1" class="current">Everyone (0)</li>
|
<li tab-id="tab-1" class="current">Everyone (0)</li>
|
||||||
|
@ -291,7 +275,7 @@
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Modal -->
|
<!-- Modal for jump to-->
|
||||||
<div class="modal fade" id="myModal" tabindex="-1" role="dialog" aria-labelledby="myModalLabel">
|
<div class="modal fade" id="myModal" tabindex="-1" role="dialog" aria-labelledby="myModalLabel">
|
||||||
<div class="modal-dialog" role="document">
|
<div class="modal-dialog" role="document">
|
||||||
<div class="modal-content">
|
<div class="modal-content">
|
||||||
|
@ -310,6 +294,39 @@
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<!-- Modal for visibility-->
|
||||||
|
<div class="modal fade" id="myModal2" tabindex="-1" role="dialog" aria-labelledby="myModalLabel">
|
||||||
|
<div class="modal-dialog" role="document">
|
||||||
|
<div class="modal-content">
|
||||||
|
<div class="modal-header">
|
||||||
|
<button type="button" class="close" data-dismiss="modal" aria-label="Close"><span aria-hidden="true">×</span></button>
|
||||||
|
<h4 class="modal-title" id="myModalLabel">Change your visibility setting</h4>
|
||||||
|
</div>
|
||||||
|
<div class="modal-body">
|
||||||
|
<ul class="dropdown-menu" aria-labelledby="visibility-toggle">
|
||||||
|
<li class="visibility-option" data-visibility="all">
|
||||||
|
<h5>Online</h5>
|
||||||
|
<p>You will be shown online to everyone else. Anybody will be able to find you from the users online list and jump to your current location.</p>
|
||||||
|
</li>
|
||||||
|
<li role="separator" class="divider"></li>
|
||||||
|
<li class="visibility-option" data-visibility="friends">
|
||||||
|
<h5>Available to Friends Only</h5>
|
||||||
|
<p>You will be shown online only to users you have added as friends. Other users may still interact with you in the same domain, but they won't be able to find you from the users online list.</p>
|
||||||
|
</li>
|
||||||
|
<li role="separator" class="divider"></li>
|
||||||
|
<li class="visibility-option" data-visibility="none">
|
||||||
|
<h5>Appear Offline</h5>
|
||||||
|
<p>No one will be able to find you from the users online list. However, this does not prevent other users in the same domain from interacting with you. For a complete "Do not disturb" mode, you may want to go to your own private domain and set allow entering to no one.</p>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
<div class="modal-footer">
|
||||||
|
<input type="button" data-dismiss="modal" value="OK">
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
<script src="https://ajax.googleapis.com/ajax/libs/jquery/3.1.1/jquery.min.js"></script>
|
<script src="https://ajax.googleapis.com/ajax/libs/jquery/3.1.1/jquery.min.js"></script>
|
||||||
<script src="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/js/bootstrap.min.js" integrity="sha384-Tc5IQib027qvyjSMfHjOMaLkfuWVxZxUPnCJA7l2mCWNIpG9mGCD8wGNIcPD7Txa" crossorigin="anonymous"></script>
|
<script src="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/js/bootstrap.min.js" integrity="sha384-Tc5IQib027qvyjSMfHjOMaLkfuWVxZxUPnCJA7l2mCWNIpG9mGCD8wGNIcPD7Txa" crossorigin="anonymous"></script>
|
||||||
<script>
|
<script>
|
||||||
|
@ -321,15 +338,19 @@
|
||||||
function displayUsers(data, element) {
|
function displayUsers(data, element) {
|
||||||
element.empty();
|
element.empty();
|
||||||
for (var i = 0; i < data.users.length; i++) {
|
for (var i = 0; i < data.users.length; i++) {
|
||||||
// Don't display users who aren't in a domain
|
// Display users who aren't in a domain differently
|
||||||
if (typeof data.users[i].location.root.name === "undefined") {
|
if (typeof data.users[i].location.root === "undefined" || typeof data.users[i].location.root.name === "undefined") {
|
||||||
console.log(data.users[i].username + "is online but not in a domain");
|
console.log(data.users[i].username + "is online but not in a domain");
|
||||||
$("#dev-div").append("<p>" + data.users[i].username + "is online but not in a domain</p>");
|
$("#dev-div").append("<p>" + data.users[i].username + "is online but not in a domain</p>");
|
||||||
|
$("<li></li>", {
|
||||||
|
"data-toggle": "modal",
|
||||||
|
"data-target": "#myModal",
|
||||||
|
"data-username": data.users[i].username,
|
||||||
|
"data-placename": "",
|
||||||
|
text: data.users[i].username
|
||||||
|
}).appendTo(element);
|
||||||
} else {
|
} else {
|
||||||
$("#dev-div").append("<li>" + data.users[i].username + " @ " + data.users[i].location.root.name + "</li>");
|
$("#dev-div").append("<li>" + data.users[i].username + " @ " + data.users[i].location.root.name + "</li>");
|
||||||
if (data.users[i].username === myUsername) {
|
|
||||||
$("#user-info-div h4").text(data.users[i].username + " @ " + data.users[i].location.root.name);
|
|
||||||
} else {
|
|
||||||
console.log(data.users[i].username + " @ " + data.users[i].location.root.name);
|
console.log(data.users[i].username + " @ " + data.users[i].location.root.name);
|
||||||
// Create a list item and put user info in data-* attributes, also make it trigger the jump to confirmation modal
|
// Create a list item and put user info in data-* attributes, also make it trigger the jump to confirmation modal
|
||||||
$("<li></li>", {
|
$("<li></li>", {
|
||||||
|
@ -342,9 +363,28 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function isOtherUsers(user) {
|
||||||
|
return user.username != myUsername;
|
||||||
}
|
}
|
||||||
|
|
||||||
function processData(data, type) {
|
function processData(data, type) {
|
||||||
|
// Filter out yourself from the users list
|
||||||
|
data.users = data.users.filter(isOtherUsers);
|
||||||
|
|
||||||
|
// Sort array alphabetically by username
|
||||||
|
data.users.sort(function(a, b) {
|
||||||
|
var nameA = a.username.toLowerCase();
|
||||||
|
var nameB = b.username.toLowerCase();
|
||||||
|
if (nameA < nameB) {
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
if (nameA > nameB) {
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
});
|
||||||
|
|
||||||
var num = data.users.length;
|
var num = data.users.length;
|
||||||
if (type === "everyone") {
|
if (type === "everyone") {
|
||||||
$(".tabs li:nth-child(1)").text("Everyone (" + num + ")");
|
$(".tabs li:nth-child(1)").text("Everyone (" + num + ")");
|
||||||
|
@ -418,7 +458,11 @@
|
||||||
var placename = li.data("placename");
|
var placename = li.data("placename");
|
||||||
// Write info to the modal
|
// Write info to the modal
|
||||||
var modal = $(this);
|
var modal = $(this);
|
||||||
|
if (placename === "") {
|
||||||
|
modal.find(".modal-title").text("Jump to " + username);
|
||||||
|
} else {
|
||||||
modal.find(".modal-title").text("Jump to " + username + " @ " + placename);
|
modal.find(".modal-title").text("Jump to " + username + " @ " + placename);
|
||||||
|
}
|
||||||
$("#jump-to-confirm-button").data("username", username);
|
$("#jump-to-confirm-button").data("username", username);
|
||||||
})
|
})
|
||||||
|
|
||||||
|
@ -434,8 +478,10 @@
|
||||||
|
|
||||||
// Click listener for toggling who can see me
|
// Click listener for toggling who can see me
|
||||||
$(".visibility-option").click(function() {
|
$(".visibility-option").click(function() {
|
||||||
|
$(".visibility-option").removeClass("current");
|
||||||
|
$(this).addClass("current");
|
||||||
myVisibility = $(this).data("visibility");
|
myVisibility = $(this).data("visibility");
|
||||||
var newButtonText = $(this).find("h6").text();
|
var newButtonText = $(this).find("h5").text();
|
||||||
$("#visibility-toggle").html(newButtonText + "<span class='glyphicon glyphicon-menu-down'></span>");
|
$("#visibility-toggle").html(newButtonText + "<span class='glyphicon glyphicon-menu-down'></span>");
|
||||||
var visibilityObject = {
|
var visibilityObject = {
|
||||||
"type": "toggle-visibility",
|
"type": "toggle-visibility",
|
||||||
|
|
|
@ -35,7 +35,7 @@
|
||||||
|
|
||||||
var tablet = Tablet.getTablet("com.highfidelity.interface.tablet.system");
|
var tablet = Tablet.getTablet("com.highfidelity.interface.tablet.system");
|
||||||
var button = tablet.addButton({
|
var button = tablet.addButton({
|
||||||
icon: "icons/tablet-icons/people-i.svg",
|
icon: "icons/tablet-icons/users-i.svg",
|
||||||
text: "USERS",
|
text: "USERS",
|
||||||
sortOrder: 11
|
sortOrder: 11
|
||||||
});
|
});
|
||||||
|
|
Loading…
Reference in a new issue