mirror of
https://github.com/overte-org/overte.git
synced 2025-06-05 07:00:34 +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 };
|
||||
float AudioMixer::_noiseMutingThreshold{ DEFAULT_NOISE_MUTING_THRESHOLD };
|
||||
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;
|
||||
QVector<AudioMixer::ZoneSettings> AudioMixer::_zoneSettings;
|
||||
QVector<AudioMixer::ReverbSettings> AudioMixer::_zoneReverbSettings;
|
||||
|
||||
AudioMixer::AudioMixer(ReceivedMessage& 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& packetReceiver = nodeList->getPacketReceiver();
|
||||
|
||||
packetReceiver.registerListenerForTypes({ PacketType::MicrophoneAudioNoEcho, PacketType::MicrophoneAudioWithEcho,
|
||||
PacketType::InjectAudio, PacketType::AudioStreamStats },
|
||||
this, "handleAudioPacket");
|
||||
packetReceiver.registerListenerForTypes({ PacketType::SilentAudioFrame }, this, "handleSilentAudioPacket");
|
||||
packetReceiver.registerListener(PacketType::NegotiateAudioFormat, this, "handleNegotiateAudioFormat");
|
||||
// packets whose consequences are limited to their own node can be parallelized
|
||||
packetReceiver.registerListenerForTypes({
|
||||
PacketType::MicrophoneAudioNoEcho,
|
||||
PacketType::MicrophoneAudioWithEcho,
|
||||
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::NodeIgnoreRequest, this, "handleNodeIgnoreRequestPacket");
|
||||
packetReceiver.registerListener(PacketType::KillAvatar, this, "handleKillAvatarPacket");
|
||||
packetReceiver.registerListener(PacketType::NodeMuteRequest, this, "handleNodeMuteRequestPacket");
|
||||
packetReceiver.registerListener(PacketType::RadiusIgnoreRequest, this, "handleRadiusIgnoreRequestPacket");
|
||||
packetReceiver.registerListener(PacketType::RequestsDomainListData, this, "handleRequestsDomainListDataPacket");
|
||||
packetReceiver.registerListener(PacketType::PerAvatarGainSet, this, "handlePerAvatarGainSetDataPacket");
|
||||
packetReceiver.registerListener(PacketType::KillAvatar, this, "handleKillAvatarPacket");
|
||||
|
||||
connect(nodeList.data(), &NodeList::nodeKilled, this, &AudioMixer::handleNodeKilled);
|
||||
}
|
||||
|
||||
void AudioMixer::handleAudioPacket(QSharedPointer<ReceivedMessage> message, SharedNodePointer sendingNode) {
|
||||
getOrCreateClientData(sendingNode.data());
|
||||
DependencyManager::get<NodeList>()->updateNodeWithDataFromPacket(message, sendingNode);
|
||||
}
|
||||
void AudioMixer::queueAudioPacket(QSharedPointer<ReceivedMessage> message, SharedNodePointer node) {
|
||||
if (message->getType() == PacketType::SilentAudioFrame) {
|
||||
_numSilentPackets++;
|
||||
}
|
||||
|
||||
void AudioMixer::handleSilentAudioPacket(QSharedPointer<ReceivedMessage> message, SharedNodePointer sendingNode) {
|
||||
_numSilentPackets++;
|
||||
getOrCreateClientData(sendingNode.data());
|
||||
DependencyManager::get<NodeList>()->updateNodeWithDataFromPacket(message, sendingNode);
|
||||
getOrCreateClientData(node.data())->queuePacket(message, node);
|
||||
}
|
||||
|
||||
void AudioMixer::handleMuteEnvironmentPacket(QSharedPointer<ReceivedMessage> message, SharedNodePointer sendingNode) {
|
||||
|
@ -119,69 +133,28 @@ InputPluginList getInputPlugins() {
|
|||
return result;
|
||||
}
|
||||
|
||||
void saveInputPluginSettings(const InputPluginList& plugins) {
|
||||
}
|
||||
// must be here to satisfy a reference in PluginManager::saveSettings()
|
||||
void saveInputPluginSettings(const InputPluginList& plugins) {}
|
||||
|
||||
|
||||
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;
|
||||
const std::pair<QString, CodecPluginPointer> AudioMixer::negotiateCodec(std::vector<QString> codecs) {
|
||||
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
|
||||
const int MAX_PREFERENCE = 99999;
|
||||
int preferredCodecIndex = MAX_PREFERENCE;
|
||||
QString preferredCodec;
|
||||
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;
|
||||
}
|
||||
// choose the preferred, available codec
|
||||
if (preference >= 0 && preference < minPreference) {
|
||||
minPreference = preference;
|
||||
selectedCodecName = codec;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
auto clientData = getOrCreateClientData(sendingNode.data());
|
||||
clientData->setupCodec(selectedCodec, selectedCodecName);
|
||||
qDebug() << "selectedCodecName:" << selectedCodecName;
|
||||
clientData->sendSelectAudioFormat(sendingNode, selectedCodecName);
|
||||
return std::make_pair(selectedCodecName, _availableCodecs[selectedCodecName]);
|
||||
}
|
||||
|
||||
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) {
|
||||
auto injectorClientData = qobject_cast<AudioMixerClientData*>(sender());
|
||||
if (injectorClientData) {
|
||||
|
@ -323,6 +260,7 @@ void AudioMixer::sendStatsPacket() {
|
|||
addTiming(_prepareTiming, "prepare");
|
||||
addTiming(_mixTiming, "mix");
|
||||
addTiming(_eventsTiming, "events");
|
||||
addTiming(_packetsTiming, "packets");
|
||||
|
||||
#ifdef HIFI_AUDIO_MIXER_DEBUG
|
||||
timingStats["ns_per_mix"] = (_stats.totalMixes > 0) ? (float)(_stats.mixTime / _stats.totalMixes) : 0;
|
||||
|
@ -452,19 +390,27 @@ void AudioMixer::start() {
|
|||
++frame;
|
||||
++_numStatFrames;
|
||||
|
||||
// play nice with qt event-looping
|
||||
// process queued events (networking, global audio packets, &c.)
|
||||
{
|
||||
auto eventsTimer = _eventsTiming.timer();
|
||||
|
||||
// since we're a while loop we need to yield to qt's event processing
|
||||
QCoreApplication::processEvents();
|
||||
|
||||
if (_isFinished) {
|
||||
// alert qt eventing that this is finished
|
||||
QCoreApplication::sendPostedEvents(this, QEvent::DeferredDelete);
|
||||
break;
|
||||
// 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) {
|
||||
// alert qt eventing that this is finished
|
||||
QCoreApplication::sendPostedEvents(this, QEvent::DeferredDelete);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -629,7 +575,8 @@ void AudioMixer::parseSettingsObject(const QJsonObject &settingsObject) {
|
|||
|
||||
const QString CODEC_PREFERENCE_ORDER = "codec_preference_order";
|
||||
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;
|
||||
}
|
||||
|
||||
|
|
|
@ -49,6 +49,7 @@ public:
|
|||
static const QHash<QString, AABox>& getAudioZones() { return _audioZones; }
|
||||
static const QVector<ZoneSettings>& getZoneSettings() { return _zoneSettings; }
|
||||
static const QVector<ReverbSettings>& getReverbSettings() { return _zoneReverbSettings; }
|
||||
static const std::pair<QString, CodecPluginPointer> negotiateCodec(std::vector<QString> codecs);
|
||||
|
||||
public slots:
|
||||
void run() override;
|
||||
|
@ -56,20 +57,14 @@ public slots:
|
|||
|
||||
private slots:
|
||||
// packet handlers
|
||||
void handleAudioPacket(QSharedPointer<ReceivedMessage> packet, SharedNodePointer sendingNode);
|
||||
void handleSilentAudioPacket(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 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 start();
|
||||
|
||||
private:
|
||||
// mixing helpers
|
||||
|
@ -93,8 +88,6 @@ private:
|
|||
int _numStatFrames { 0 };
|
||||
AudioMixerStats _stats;
|
||||
|
||||
QString _codecPreferenceOrder;
|
||||
|
||||
AudioMixerSlavePool _slavePool;
|
||||
|
||||
class Timer {
|
||||
|
@ -124,13 +117,17 @@ private:
|
|||
Timer _prepareTiming;
|
||||
Timer _mixTiming;
|
||||
Timer _eventsTiming;
|
||||
Timer _packetsTiming;
|
||||
|
||||
static int _numStaticJitterFrames; // -1 denotes dynamic jitter buffering
|
||||
static float _noiseMutingThreshold;
|
||||
static float _attenuationPerDoublingInDistance;
|
||||
static std::map<QString, CodecPluginPointer> _availableCodecs;
|
||||
static QStringList _codecPreferenceOrder;
|
||||
static QHash<QString, AABox> _audioZones;
|
||||
static QVector<ZoneSettings> _zoneSettings;
|
||||
static QVector<ReverbSettings> _zoneReverbSettings;
|
||||
|
||||
};
|
||||
|
||||
#endif // hifi_AudioMixer_h
|
||||
|
|
|
@ -19,6 +19,7 @@
|
|||
|
||||
#include "InjectedAudioStream.h"
|
||||
|
||||
#include "AudioHelpers.h"
|
||||
#include "AudioMixer.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() {
|
||||
QReadLocker readLocker { &_streamsLock };
|
||||
|
|
|
@ -12,6 +12,8 @@
|
|||
#ifndef hifi_AudioMixerClientData_h
|
||||
#define hifi_AudioMixerClientData_h
|
||||
|
||||
#include <queue>
|
||||
|
||||
#include <QtCore/QJsonObject>
|
||||
|
||||
#include <AABox.h>
|
||||
|
@ -34,6 +36,9 @@ public:
|
|||
using SharedStreamPointer = std::shared_ptr<PositionalAudioStream>;
|
||||
using AudioStreamMap = std::unordered_map<QUuid, SharedStreamPointer>;
|
||||
|
||||
void queuePacket(QSharedPointer<ReceivedMessage> packet, SharedNodePointer node);
|
||||
void processPackets();
|
||||
|
||||
// locks the mutex to make a copy
|
||||
AudioStreamMap getAudioStreams() { QReadLocker readLock { &_streamsLock }; return _audioStreams; }
|
||||
AvatarAudioStream* getAvatarAudioStream();
|
||||
|
@ -56,7 +61,13 @@ public:
|
|||
|
||||
void removeAgentAvatarAudioStream();
|
||||
|
||||
// packet parsers
|
||||
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
|
||||
int checkBuffersBeforeFrameSend();
|
||||
|
@ -105,11 +116,15 @@ public slots:
|
|||
void sendSelectAudioFormat(SharedNodePointer node, const QString& selectedCodecName);
|
||||
|
||||
private:
|
||||
using IgnoreZone = AABox;
|
||||
struct PacketQueue : public std::queue<QSharedPointer<ReceivedMessage>> {
|
||||
QWeakPointer<Node> node;
|
||||
};
|
||||
PacketQueue _packetQueue;
|
||||
|
||||
QReadWriteLock _streamsLock;
|
||||
AudioStreamMap _audioStreams; // microphone stream from avatar is stored under key of null UUID
|
||||
|
||||
using IgnoreZone = AABox;
|
||||
class IgnoreZoneMemo {
|
||||
public:
|
||||
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,
|
||||
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;
|
||||
_end = end;
|
||||
_frame = frame;
|
||||
|
|
|
@ -30,9 +30,13 @@ class AudioMixerSlave {
|
|||
public:
|
||||
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
|
||||
void mix(const SharedNodePointer& node);
|
||||
|
||||
|
|
|
@ -21,7 +21,7 @@ void AudioMixerSlaveThread::run() {
|
|||
// iterate over all available nodes
|
||||
SharedNodePointer node;
|
||||
while (try_pop(node)) {
|
||||
mix(node);
|
||||
(this->*_function)(node);
|
||||
}
|
||||
|
||||
bool stopping = _stop;
|
||||
|
@ -41,7 +41,11 @@ void AudioMixerSlaveThread::wait() {
|
|||
});
|
||||
++_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) {
|
||||
|
@ -64,16 +68,31 @@ bool AudioMixerSlaveThread::try_pop(SharedNodePointer& node) {
|
|||
static AudioMixerSlave slave;
|
||||
#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) {
|
||||
_begin = begin;
|
||||
_end = end;
|
||||
_function = &AudioMixerSlave::mix;
|
||||
_configure = [&](AudioMixerSlave& slave) {
|
||||
slave.configureMix(_begin, _end, _frame, _throttlingRatio);
|
||||
};
|
||||
_frame = frame;
|
||||
_throttlingRatio = throttlingRatio;
|
||||
|
||||
run(begin, end);
|
||||
}
|
||||
|
||||
void AudioMixerSlavePool::run(ConstIter begin, ConstIter end) {
|
||||
_begin = begin;
|
||||
_end = end;
|
||||
|
||||
#ifdef AUDIO_SINGLE_THREADED
|
||||
slave.configure(_begin, _end, frame, throttlingRatio);
|
||||
_configure(slave);
|
||||
std::for_each(begin, end, [&](const SharedNodePointer& node) {
|
||||
slave.mix(node);
|
||||
_function(slave, node);
|
||||
});
|
||||
#else
|
||||
// fill the queue
|
||||
|
@ -84,7 +103,7 @@ void AudioMixerSlavePool::mix(ConstIter begin, ConstIter end, unsigned int frame
|
|||
{
|
||||
Lock lock(_mutex);
|
||||
|
||||
// mix
|
||||
// run
|
||||
_numStarted = _numFinished = 0;
|
||||
_slaveCondition.notify_all();
|
||||
|
||||
|
|
|
@ -43,6 +43,7 @@ private:
|
|||
bool try_pop(SharedNodePointer& node);
|
||||
|
||||
AudioMixerSlavePool& _pool;
|
||||
void (AudioMixerSlave::*_function)(const SharedNodePointer& node) { nullptr };
|
||||
bool _stop { false };
|
||||
};
|
||||
|
||||
|
@ -60,6 +61,9 @@ public:
|
|||
AudioMixerSlavePool(int numThreads = QThread::idealThreadCount()) { setNumThreads(numThreads); }
|
||||
~AudioMixerSlavePool() { resize(0); }
|
||||
|
||||
// process packets on slave threads
|
||||
void processPackets(ConstIter begin, ConstIter end);
|
||||
|
||||
// mix on slave threads
|
||||
void mix(ConstIter begin, ConstIter end, unsigned int frame, float throttlingRatio);
|
||||
|
||||
|
@ -70,6 +74,7 @@ public:
|
|||
int numThreads() { return _numThreads; }
|
||||
|
||||
private:
|
||||
void run(ConstIter begin, ConstIter end);
|
||||
void resize(int numThreads);
|
||||
|
||||
std::vector<std::unique_ptr<AudioMixerSlaveThread>> _slaves;
|
||||
|
@ -82,6 +87,8 @@ private:
|
|||
Mutex _mutex;
|
||||
ConditionVariable _slaveCondition;
|
||||
ConditionVariable _poolCondition;
|
||||
void (AudioMixerSlave::*_function)(const SharedNodePointer& node);
|
||||
std::function<void(AudioMixerSlave&)> _configure;
|
||||
int _numThreads { 0 };
|
||||
int _numStarted { 0 }; // guarded by _mutex
|
||||
int _numFinished { 0 }; // guarded by _mutex
|
||||
|
|
|
@ -577,7 +577,7 @@ SharedNodePointer LimitedNodeList::addOrUpdateNode(const QUuid& uuid, NodeType_t
|
|||
|
||||
if (it != _nodeHash.end()) {
|
||||
SharedNodePointer& matchingNode = it->second;
|
||||
|
||||
|
||||
matchingNode->setPublicSocket(publicSocket);
|
||||
matchingNode->setLocalSocket(localSocket);
|
||||
matchingNode->setPermissions(permissions);
|
||||
|
@ -717,14 +717,20 @@ SharedNodePointer LimitedNodeList::soloNodeOfType(NodeType_t nodeType) {
|
|||
});
|
||||
}
|
||||
|
||||
void LimitedNodeList::getPacketStats(float& packetsPerSecond, float& bytesPerSecond) {
|
||||
packetsPerSecond = (float) _numCollectedPackets / ((float) _packetStatTimer.elapsed() / 1000.0f);
|
||||
bytesPerSecond = (float) _numCollectedBytes / ((float) _packetStatTimer.elapsed() / 1000.0f);
|
||||
void LimitedNodeList::getPacketStats(float& packetsInPerSecond, float& bytesInPerSecond, float& packetsOutPerSecond, float& bytesOutPerSecond) {
|
||||
packetsInPerSecond = (float) getPacketReceiver().getInPacketCount() / ((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() {
|
||||
getPacketReceiver().resetCounters();
|
||||
|
||||
_numCollectedPackets = 0;
|
||||
_numCollectedBytes = 0;
|
||||
|
||||
_packetStatTimer.restart();
|
||||
}
|
||||
|
||||
|
|
|
@ -161,7 +161,7 @@ public:
|
|||
unsigned int broadcastToNodes(std::unique_ptr<NLPacket> packet, const NodeSet& destinationNodeTypes);
|
||||
SharedNodePointer soloNodeOfType(NodeType_t nodeType);
|
||||
|
||||
void getPacketStats(float &packetsPerSecond, float &bytesPerSecond);
|
||||
void getPacketStats(float& packetsInPerSecond, float& bytesInPerSecond, float& packetsOutPerSecond, float& bytesOutPerSecond);
|
||||
void resetPacketStats();
|
||||
|
||||
std::unique_ptr<NLPacket> constructPingPacket(PingType_t pingType = PingType::Agnostic);
|
||||
|
|
|
@ -45,9 +45,9 @@ void ThreadedAssignment::setFinished(bool isFinished) {
|
|||
if (_isFinished) {
|
||||
|
||||
qCDebug(networking) << "ThreadedAssignment::setFinished(true) called - finishing up.";
|
||||
|
||||
|
||||
auto nodeList = DependencyManager::get<NodeList>();
|
||||
|
||||
|
||||
auto& packetReceiver = nodeList->getPacketReceiver();
|
||||
|
||||
// we should de-register immediately for any of our packets
|
||||
|
@ -55,7 +55,7 @@ void ThreadedAssignment::setFinished(bool isFinished) {
|
|||
|
||||
// we should also tell the packet receiver to drop packets while we're cleaning up
|
||||
packetReceiver.setShouldDropPackets(true);
|
||||
|
||||
|
||||
// send a disconnect packet to the domain
|
||||
nodeList->getDomainHandler().disconnect();
|
||||
|
||||
|
@ -92,12 +92,17 @@ void ThreadedAssignment::commonInit(const QString& targetName, NodeType_t nodeTy
|
|||
void ThreadedAssignment::addPacketStatsAndSendStatsPacket(QJsonObject statsObject) {
|
||||
auto nodeList = DependencyManager::get<NodeList>();
|
||||
|
||||
float packetsPerSecond, bytesPerSecond;
|
||||
nodeList->getPacketStats(packetsPerSecond, bytesPerSecond);
|
||||
float packetsInPerSecond, bytesInPerSecond, packetsOutPerSecond, bytesOutPerSecond;
|
||||
nodeList->getPacketStats(packetsInPerSecond, bytesInPerSecond, packetsOutPerSecond, bytesOutPerSecond);
|
||||
nodeList->resetPacketStats();
|
||||
|
||||
statsObject["packets_per_second"] = packetsPerSecond;
|
||||
statsObject["bytes_per_second"] = bytesPerSecond;
|
||||
QJsonObject ioStats;
|
||||
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);
|
||||
}
|
||||
|
|
|
@ -123,8 +123,6 @@ const CodecPluginList& PluginManager::getCodecPlugins() {
|
|||
static CodecPluginList codecPlugins;
|
||||
static std::once_flag once;
|
||||
std::call_once(once, [&] {
|
||||
//codecPlugins = ::getCodecPlugins();
|
||||
|
||||
// Now grab the dynamic plugins
|
||||
for (auto loader : getLoadedPlugins()) {
|
||||
CodecProvider* codecProvider = qobject_cast<CodecProvider*>(loader->instance());
|
||||
|
|
|
@ -145,10 +145,8 @@ void Deck::processFrames() {
|
|||
}
|
||||
|
||||
if (!nextClip) {
|
||||
qCDebug(recordingLog) << "No more frames available";
|
||||
// No more frames available, so handle the end of playback
|
||||
if (_loop) {
|
||||
qCDebug(recordingLog) << "Looping enabled, seeking back to beginning";
|
||||
// If we have looping enabled, start the playback over
|
||||
seek(0);
|
||||
// FIXME configure the recording scripting interface to reset the avatar basis on a loop
|
||||
|
|
|
@ -24,19 +24,18 @@
|
|||
}
|
||||
|
||||
.top-bar {
|
||||
width: 100%;
|
||||
height: 90px;
|
||||
background: linear-gradient(#2b2b2b, #1e1e1e);
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.top-bar .myContainer {
|
||||
padding-left: 30px;
|
||||
padding-right: 30px;
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
margin-left: 30px;
|
||||
margin-right: 30px;
|
||||
height: 100%;
|
||||
position: fixed;
|
||||
width: 480px;
|
||||
top: 0;
|
||||
z-index: 1;
|
||||
justify-content: space-between;
|
||||
}
|
||||
|
||||
#refresh-button {
|
||||
|
@ -46,6 +45,7 @@
|
|||
|
||||
.main {
|
||||
padding: 30px;
|
||||
margin-top: 90px;
|
||||
}
|
||||
|
||||
#user-info-div {
|
||||
|
@ -219,7 +219,11 @@
|
|||
}
|
||||
|
||||
.dropdown-menu {
|
||||
width: 280px;
|
||||
width: 350px;
|
||||
border: 0;
|
||||
display: block;
|
||||
float: none;
|
||||
position: inherit;
|
||||
}
|
||||
|
||||
.dropdown-menu li {
|
||||
|
@ -231,52 +235,32 @@
|
|||
padding: 0px;
|
||||
}
|
||||
|
||||
.dropdown-menu li:hover {
|
||||
.dropdown-menu li.current {
|
||||
background: #dcdcdc;
|
||||
}
|
||||
|
||||
.dropdown-menu li h6 {
|
||||
.dropdown-menu li h5 {
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.dropdown-menu li p {
|
||||
font-size: 11px;
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="top-bar">
|
||||
<div class="myContainer">
|
||||
<div>Users Online</div>
|
||||
<img id="refresh-button" onclick="pollUsers()" src="https://hifi-content.s3.amazonaws.com/faye/tablet-dev/refresh-icon.svg"></img>
|
||||
</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>
|
||||
</div>
|
||||
<div class="main">
|
||||
<div id="user-info-div">
|
||||
<h4></h4>
|
||||
<div class="dropdown">
|
||||
<button id="visibility-toggle" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false">
|
||||
Online
|
||||
<span class="glyphicon glyphicon-menu-down"></span>
|
||||
</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>
|
||||
<button id="visibility-toggle" data-toggle="modal" data-target="#myModal2" aria-haspopup="true" aria-expanded="false">
|
||||
Online
|
||||
<span class="glyphicon glyphicon-menu-down"></span>
|
||||
</button>
|
||||
</div>
|
||||
<ul class="tabs">
|
||||
<li tab-id="tab-1" class="current">Everyone (0)</li>
|
||||
|
@ -291,7 +275,7 @@
|
|||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Modal -->
|
||||
<!-- Modal for jump to-->
|
||||
<div class="modal fade" id="myModal" tabindex="-1" role="dialog" aria-labelledby="myModalLabel">
|
||||
<div class="modal-dialog" role="document">
|
||||
<div class="modal-content">
|
||||
|
@ -310,6 +294,39 @@
|
|||
</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://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/js/bootstrap.min.js" integrity="sha384-Tc5IQib027qvyjSMfHjOMaLkfuWVxZxUPnCJA7l2mCWNIpG9mGCD8wGNIcPD7Txa" crossorigin="anonymous"></script>
|
||||
<script>
|
||||
|
@ -321,30 +338,53 @@
|
|||
function displayUsers(data, element) {
|
||||
element.empty();
|
||||
for (var i = 0; i < data.users.length; i++) {
|
||||
// Don't display users who aren't in a domain
|
||||
if (typeof data.users[i].location.root.name === "undefined") {
|
||||
// Display users who aren't in a domain differently
|
||||
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");
|
||||
$("#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 {
|
||||
$("#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);
|
||||
// Create a list item and put user info in data-* attributes, also make it trigger the jump to confirmation modal
|
||||
$("<li></li>", {
|
||||
"data-toggle": "modal",
|
||||
"data-target": "#myModal",
|
||||
"data-username": data.users[i].username,
|
||||
"data-placename": data.users[i].location.root.name,
|
||||
text: data.users[i].username + " @ " + data.users[i].location.root.name
|
||||
}).appendTo(element);
|
||||
}
|
||||
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
|
||||
$("<li></li>", {
|
||||
"data-toggle": "modal",
|
||||
"data-target": "#myModal",
|
||||
"data-username": data.users[i].username,
|
||||
"data-placename": data.users[i].location.root.name,
|
||||
text: data.users[i].username + " @ " + data.users[i].location.root.name
|
||||
}).appendTo(element);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function isOtherUsers(user) {
|
||||
return user.username != myUsername;
|
||||
}
|
||||
|
||||
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;
|
||||
if (type === "everyone") {
|
||||
$(".tabs li:nth-child(1)").text("Everyone (" + num + ")");
|
||||
|
@ -418,7 +458,11 @@
|
|||
var placename = li.data("placename");
|
||||
// Write info to the modal
|
||||
var modal = $(this);
|
||||
modal.find(".modal-title").text("Jump to " + username + " @ " + placename);
|
||||
if (placename === "") {
|
||||
modal.find(".modal-title").text("Jump to " + username);
|
||||
} else {
|
||||
modal.find(".modal-title").text("Jump to " + username + " @ " + placename);
|
||||
}
|
||||
$("#jump-to-confirm-button").data("username", username);
|
||||
})
|
||||
|
||||
|
@ -434,8 +478,10 @@
|
|||
|
||||
// Click listener for toggling who can see me
|
||||
$(".visibility-option").click(function() {
|
||||
$(".visibility-option").removeClass("current");
|
||||
$(this).addClass("current");
|
||||
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>");
|
||||
var visibilityObject = {
|
||||
"type": "toggle-visibility",
|
||||
|
|
|
@ -35,7 +35,7 @@
|
|||
|
||||
var tablet = Tablet.getTablet("com.highfidelity.interface.tablet.system");
|
||||
var button = tablet.addButton({
|
||||
icon: "icons/tablet-icons/people-i.svg",
|
||||
icon: "icons/tablet-icons/users-i.svg",
|
||||
text: "USERS",
|
||||
sortOrder: 11
|
||||
});
|
||||
|
|
Loading…
Reference in a new issue