mirror of
https://github.com/lubosz/overte.git
synced 2025-04-27 00:35:37 +02:00
Merge branch 'master' of https://github.com/highfidelity/hifi
This commit is contained in:
commit
3bd8cddc31
10 changed files with 430 additions and 86 deletions
assignment-client/src
examples
interface/src
libraries
|
@ -26,8 +26,7 @@
|
|||
Agent::Agent(const QByteArray& packet) :
|
||||
ThreadedAssignment(packet),
|
||||
_voxelEditSender(),
|
||||
_particleEditSender(),
|
||||
_avatarAudioStream(NULL)
|
||||
_particleEditSender()
|
||||
{
|
||||
// be the parent of the script engine so it gets moved when we do
|
||||
_scriptEngine.setParent(this);
|
||||
|
@ -36,30 +35,6 @@ Agent::Agent(const QByteArray& packet) :
|
|||
_scriptEngine.getParticlesScriptingInterface()->setPacketSender(&_particleEditSender);
|
||||
}
|
||||
|
||||
Agent::~Agent() {
|
||||
delete _avatarAudioStream;
|
||||
}
|
||||
|
||||
const int SCRIPT_AUDIO_BUFFER_SAMPLES = floor(((SCRIPT_DATA_CALLBACK_USECS * SAMPLE_RATE) / (1000 * 1000)) + 0.5);
|
||||
|
||||
void Agent::setSendAvatarAudioStream(bool sendAvatarAudioStream) {
|
||||
if (sendAvatarAudioStream) {
|
||||
// the agentAudioStream number of samples is related to the ScriptEngine callback rate
|
||||
_avatarAudioStream = new int16_t[SCRIPT_AUDIO_BUFFER_SAMPLES];
|
||||
|
||||
// fill the _audioStream with zeroes to start
|
||||
memset(_avatarAudioStream, 0, SCRIPT_AUDIO_BUFFER_SAMPLES * sizeof(int16_t));
|
||||
|
||||
_scriptEngine.setNumAvatarAudioBufferSamples(SCRIPT_AUDIO_BUFFER_SAMPLES);
|
||||
_scriptEngine.setAvatarAudioBuffer(_avatarAudioStream);
|
||||
} else {
|
||||
delete _avatarAudioStream;
|
||||
_avatarAudioStream = NULL;
|
||||
|
||||
_scriptEngine.setAvatarAudioBuffer(NULL);
|
||||
}
|
||||
}
|
||||
|
||||
void Agent::readPendingDatagrams() {
|
||||
QByteArray receivedPacket;
|
||||
HifiSockAddr senderSockAddr;
|
||||
|
|
|
@ -28,20 +28,24 @@ class Agent : public ThreadedAssignment {
|
|||
Q_OBJECT
|
||||
|
||||
Q_PROPERTY(bool isAvatar READ isAvatar WRITE setIsAvatar)
|
||||
Q_PROPERTY(bool sendAvatarAudioStream READ isSendingAvatarAudioStream WRITE setSendAvatarAudioStream)
|
||||
Q_PROPERTY(bool isPlayingAvatarSound READ isPlayingAvatarSound)
|
||||
Q_PROPERTY(bool isListeningToAudioStream READ isListeningToAudioStream WRITE setIsListeningToAudioStream)
|
||||
public:
|
||||
Agent(const QByteArray& packet);
|
||||
~Agent();
|
||||
|
||||
void setIsAvatar(bool isAvatar) { QMetaObject::invokeMethod(&_scriptEngine, "setIsAvatar", Q_ARG(bool, isAvatar)); }
|
||||
bool isAvatar() const { return _scriptEngine.isAvatar(); }
|
||||
|
||||
void setSendAvatarAudioStream(bool sendAvatarAudioStream);
|
||||
bool isSendingAvatarAudioStream() const { return (bool) _scriptEngine.sendsAvatarAudioStream(); }
|
||||
bool isPlayingAvatarSound() const { return _scriptEngine.isPlayingAvatarSound(); }
|
||||
|
||||
bool isListeningToAudioStream() const { return _scriptEngine.isListeningToAudioStream(); }
|
||||
void setIsListeningToAudioStream(bool isListeningToAudioStream)
|
||||
{ _scriptEngine.setIsListeningToAudioStream(isListeningToAudioStream); }
|
||||
|
||||
public slots:
|
||||
void run();
|
||||
void readPendingDatagrams();
|
||||
void playAvatarSound(Sound* avatarSound) { _scriptEngine.setAvatarSound(avatarSound); }
|
||||
|
||||
private:
|
||||
ScriptEngine _scriptEngine;
|
||||
|
@ -50,8 +54,6 @@ private:
|
|||
|
||||
ParticleTreeHeadlessViewer _particleViewer;
|
||||
VoxelTreeHeadlessViewer _voxelViewer;
|
||||
|
||||
int16_t* _avatarAudioStream;
|
||||
};
|
||||
|
||||
#endif /* defined(__hifi__Agent__) */
|
||||
|
|
|
@ -29,7 +29,6 @@ var CHANCE_OF_BIG_MOVE = 0.1;
|
|||
|
||||
var isMoving = false;
|
||||
var isTurningHead = false;
|
||||
var isPlayingAudio = false;
|
||||
|
||||
var X_MIN = 0.0;
|
||||
var X_MAX = 5.0;
|
||||
|
@ -60,20 +59,11 @@ function clamp(val, min, max){
|
|||
}
|
||||
|
||||
// Play a random sound from a list of conversational audio clips
|
||||
function audioDone() {
|
||||
isPlayingAudio = false;
|
||||
}
|
||||
|
||||
var AVERAGE_AUDIO_LENGTH = 8000;
|
||||
function playRandomSound(position) {
|
||||
if (!isPlayingAudio) {
|
||||
function playRandomSound() {
|
||||
if (!Agent.isPlayingAvatarSound) {
|
||||
var whichSound = Math.floor((Math.random() * sounds.length) % sounds.length);
|
||||
var audioOptions = new AudioInjectionOptions();
|
||||
audioOptions.volume = 0.25 + (Math.random() * 0.75);
|
||||
audioOptions.position = position;
|
||||
Audio.playSound(sounds[whichSound], audioOptions);
|
||||
isPlayingAudio = true;
|
||||
Script.setTimeout(audioDone, AVERAGE_AUDIO_LENGTH);
|
||||
Agent.playAvatarSound(sounds[whichSound]);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -104,6 +94,7 @@ Avatar.skeletonModelURL = "https://s3-us-west-1.amazonaws.com/highfidelity-publi
|
|||
Avatar.billboardURL = "https://s3-us-west-1.amazonaws.com/highfidelity-public/meshes/billboards/bot" + botNumber + ".png";
|
||||
|
||||
Agent.isAvatar = true;
|
||||
Agent.isListeningToAudioStream = true;
|
||||
|
||||
// change the avatar's position to the random one
|
||||
Avatar.position = firstPosition;
|
||||
|
@ -111,10 +102,10 @@ printVector("New bot, position = ", Avatar.position);
|
|||
|
||||
function updateBehavior(deltaTime) {
|
||||
if (Math.random() < CHANCE_OF_SOUND) {
|
||||
playRandomSound(Avatar.position);
|
||||
playRandomSound();
|
||||
}
|
||||
|
||||
if (isPlayingAudio) {
|
||||
if (Agent.isPlayingAvatarSound) {
|
||||
Avatar.handPosition = Vec3.sum(Avatar.position, Quat.getFront(Avatar.orientation));
|
||||
}
|
||||
|
||||
|
|
|
@ -24,11 +24,12 @@
|
|||
#include <QSlider>
|
||||
#include <QStandardPaths>
|
||||
#include <QUuid>
|
||||
#include <QWindow>
|
||||
#include <QHBoxLayout>
|
||||
|
||||
#include <AccountManager.h>
|
||||
#include <XmppClient.h>
|
||||
#include <UUID.h>
|
||||
#include <FileDownloader.h>
|
||||
|
||||
#include "Application.h"
|
||||
#include "Menu.h"
|
||||
|
@ -36,6 +37,7 @@
|
|||
#include "Util.h"
|
||||
#include "InfoView.h"
|
||||
#include "ui/MetavoxelEditor.h"
|
||||
#include "ModelBrowser.h"
|
||||
|
||||
|
||||
Menu* Menu::_instance = NULL;
|
||||
|
@ -692,6 +694,10 @@ void Menu::loginForCurrentDomain() {
|
|||
|
||||
void Menu::editPreferences() {
|
||||
Application* applicationInstance = Application::getInstance();
|
||||
ModelBrowser headBrowser(Head);
|
||||
ModelBrowser skeletonBrowser(Skeleton);
|
||||
|
||||
const QString BROWSE_BUTTON_TEXT = "Browse";
|
||||
|
||||
QDialog dialog(applicationInstance->getWindow());
|
||||
dialog.setWindowTitle("Interface Preferences");
|
||||
|
@ -702,17 +708,33 @@ void Menu::editPreferences() {
|
|||
QFormLayout* form = new QFormLayout();
|
||||
layout->addLayout(form, 1);
|
||||
|
||||
|
||||
QHBoxLayout headModelLayout;
|
||||
QString faceURLString = applicationInstance->getAvatar()->getHead()->getFaceModel().getURL().toString();
|
||||
QLineEdit* faceURLEdit = new QLineEdit(faceURLString);
|
||||
faceURLEdit->setMinimumWidth(QLINE_MINIMUM_WIDTH);
|
||||
faceURLEdit->setPlaceholderText(DEFAULT_HEAD_MODEL_URL.toString());
|
||||
form->addRow("Face URL:", faceURLEdit);
|
||||
|
||||
QLineEdit headURLEdit(faceURLString);
|
||||
QPushButton headBrowseButton(BROWSE_BUTTON_TEXT);
|
||||
connect(&headBrowseButton, SIGNAL(clicked()), &headBrowser, SLOT(browse()));
|
||||
connect(&headBrowser, SIGNAL(selected(QString)), &headURLEdit, SLOT(setText(QString)));
|
||||
headURLEdit.setReadOnly(true);
|
||||
headURLEdit.setMinimumWidth(QLINE_MINIMUM_WIDTH);
|
||||
headURLEdit.setPlaceholderText(DEFAULT_HEAD_MODEL_URL.toString());
|
||||
headModelLayout.addWidget(&headURLEdit);
|
||||
headModelLayout.addWidget(&headBrowseButton);
|
||||
form->addRow("Head URL:", &headModelLayout);
|
||||
|
||||
QHBoxLayout skeletonModelLayout;
|
||||
QString skeletonURLString = applicationInstance->getAvatar()->getSkeletonModel().getURL().toString();
|
||||
QLineEdit* skeletonURLEdit = new QLineEdit(skeletonURLString);
|
||||
skeletonURLEdit->setMinimumWidth(QLINE_MINIMUM_WIDTH);
|
||||
skeletonURLEdit->setPlaceholderText(DEFAULT_BODY_MODEL_URL.toString());
|
||||
form->addRow("Skeleton URL:", skeletonURLEdit);
|
||||
QLineEdit skeletonURLEdit(skeletonURLString);
|
||||
QPushButton SkeletonBrowseButton(BROWSE_BUTTON_TEXT);
|
||||
connect(&SkeletonBrowseButton, SIGNAL(clicked()), &skeletonBrowser, SLOT(browse()));
|
||||
connect(&skeletonBrowser, SIGNAL(selected(QString)), &skeletonURLEdit, SLOT(setText(QString)));
|
||||
skeletonURLEdit.setReadOnly(true);
|
||||
skeletonURLEdit.setMinimumWidth(QLINE_MINIMUM_WIDTH);
|
||||
skeletonURLEdit.setPlaceholderText(DEFAULT_BODY_MODEL_URL.toString());
|
||||
skeletonModelLayout.addWidget(&skeletonURLEdit);
|
||||
skeletonModelLayout.addWidget(&SkeletonBrowseButton);
|
||||
form->addRow("Skeleton URL:", &skeletonModelLayout);
|
||||
|
||||
|
||||
QString displayNameString = applicationInstance->getAvatar()->getDisplayName();
|
||||
QLineEdit* displayNameEdit = new QLineEdit(displayNameString);
|
||||
|
@ -774,21 +796,17 @@ void Menu::editPreferences() {
|
|||
|
||||
int ret = dialog.exec();
|
||||
if (ret == QDialog::Accepted) {
|
||||
QUrl faceModelURL(faceURLEdit->text());
|
||||
|
||||
bool shouldDispatchIdentityPacket = false;
|
||||
|
||||
if (faceModelURL.toString() != faceURLString) {
|
||||
if (headURLEdit.text() != faceURLString && !headURLEdit.text().isEmpty()) {
|
||||
// change the faceModelURL in the profile, it will also update this user's BlendFace
|
||||
applicationInstance->getAvatar()->setFaceModelURL(faceModelURL);
|
||||
applicationInstance->getAvatar()->setFaceModelURL(QUrl(headURLEdit.text()));
|
||||
shouldDispatchIdentityPacket = true;
|
||||
}
|
||||
|
||||
QUrl skeletonModelURL(skeletonURLEdit->text());
|
||||
|
||||
if (skeletonModelURL.toString() != skeletonURLString) {
|
||||
if (skeletonURLEdit.text() != skeletonURLString && !skeletonURLEdit.text().isEmpty()) {
|
||||
// change the skeletonModelURL in the profile, it will also update this user's Body
|
||||
applicationInstance->getAvatar()->setSkeletonModelURL(skeletonModelURL);
|
||||
applicationInstance->getAvatar()->setSkeletonModelURL(QUrl(skeletonURLEdit.text()));
|
||||
shouldDispatchIdentityPacket = true;
|
||||
}
|
||||
|
||||
|
|
150
interface/src/ModelBrowser.cpp
Normal file
150
interface/src/ModelBrowser.cpp
Normal file
|
@ -0,0 +1,150 @@
|
|||
//
|
||||
// ModelBrowser.cpp
|
||||
// hifi
|
||||
//
|
||||
// Created by Clement on 3/17/14.
|
||||
// Copyright (c) 2014 HighFidelity, Inc. All rights reserved.
|
||||
//
|
||||
|
||||
#include <QUrl>
|
||||
#include <QXmlStreamReader>
|
||||
#include <QEventLoop>
|
||||
#include <QMessageBox>
|
||||
#include <QGridLayout>
|
||||
#include <QDialog>
|
||||
#include <QStringListModel>
|
||||
#include <QDialogButtonBox>
|
||||
|
||||
#include <Application.h>
|
||||
|
||||
#include "ModelBrowser.h"
|
||||
|
||||
static const QString PREFIX_PARAMETER_NAME = "prefix";
|
||||
static const QString MARKER_PARAMETER_NAME = "marker";
|
||||
static const QString IS_TRUNCATED_NAME = "IsTruncated";
|
||||
static const QString CONTAINER_NAME = "Contents";
|
||||
static const QString KEY_NAME = "Key";
|
||||
|
||||
ModelBrowser::ModelBrowser(ModelType modelType, QWidget* parent) : QWidget(parent), _type(modelType) {
|
||||
QUrl url(S3_URL);
|
||||
QUrlQuery query;
|
||||
|
||||
if (_type == Head) {
|
||||
query.addQueryItem(PREFIX_PARAMETER_NAME, HEAD_MODELS_LOCATION);
|
||||
} else if (_type == Skeleton) {
|
||||
query.addQueryItem(PREFIX_PARAMETER_NAME, SKELETON_MODELS_LOCATION);
|
||||
}
|
||||
url.setQuery(query);
|
||||
|
||||
_downloader = new FileDownloader(url);
|
||||
connect(_downloader, SIGNAL(done(QNetworkReply::NetworkError)), SLOT(downloadFinished()));
|
||||
}
|
||||
|
||||
ModelBrowser::~ModelBrowser() {
|
||||
delete _downloader;
|
||||
}
|
||||
|
||||
void ModelBrowser::downloadFinished() {
|
||||
parseXML(_downloader->getData());
|
||||
}
|
||||
|
||||
void ModelBrowser::browse() {
|
||||
QDialog dialog(this);
|
||||
dialog.setWindowTitle("Browse models");
|
||||
|
||||
QGridLayout* layout = new QGridLayout(&dialog);
|
||||
dialog.setLayout(layout);
|
||||
|
||||
QLineEdit* searchBar = new QLineEdit(&dialog);
|
||||
layout->addWidget(searchBar, 0, 0);
|
||||
|
||||
ListView* listView = new ListView(&dialog);
|
||||
listView->setEditTriggers(QAbstractItemView::NoEditTriggers);
|
||||
layout->addWidget(listView, 1, 0);
|
||||
listView->connect(searchBar, SIGNAL(textChanged(const QString&)), SLOT(keyboardSearch(const QString&)));
|
||||
dialog.connect(listView, SIGNAL(doubleClicked(const QModelIndex&)), SLOT(accept()));
|
||||
|
||||
QStringListModel* model = new QStringListModel(_models.keys(), listView);
|
||||
model->sort(0);
|
||||
listView->setModel(model);
|
||||
|
||||
QDialogButtonBox* buttons = new QDialogButtonBox(QDialogButtonBox::Ok | QDialogButtonBox::Cancel);
|
||||
layout->addWidget(buttons, 2, 0);
|
||||
dialog.connect(buttons, SIGNAL(accepted()), SLOT(accept()));
|
||||
dialog.connect(buttons, SIGNAL(rejected()), SLOT(reject()));
|
||||
|
||||
if (dialog.exec() == QDialog::Rejected) {
|
||||
return;
|
||||
}
|
||||
|
||||
QString selectedKey = model->data(listView->currentIndex(), Qt::DisplayRole).toString();
|
||||
|
||||
emit selected(_models[selectedKey]);
|
||||
}
|
||||
|
||||
bool ModelBrowser::parseXML(QByteArray xmlFile) {
|
||||
QXmlStreamReader xml(xmlFile);
|
||||
QRegExp rx(".*fst");
|
||||
bool truncated = false;
|
||||
QString lastKey;
|
||||
|
||||
// Read xml until the end or an error is detected
|
||||
while(!xml.atEnd() && !xml.hasError()) {
|
||||
if(xml.tokenType() == QXmlStreamReader::StartElement && xml.name() == IS_TRUNCATED_NAME) {
|
||||
while(!(xml.tokenType() == QXmlStreamReader::EndElement && xml.name() == IS_TRUNCATED_NAME)) {
|
||||
// Let's check if there is more
|
||||
xml.readNext();
|
||||
if (xml.text().toString() == "True") {
|
||||
truncated = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if(xml.tokenType() == QXmlStreamReader::StartElement && xml.name() == CONTAINER_NAME) {
|
||||
while(!(xml.tokenType() == QXmlStreamReader::EndElement && xml.name() == CONTAINER_NAME)) {
|
||||
// If a file is find, process it
|
||||
if(xml.tokenType() == QXmlStreamReader::StartElement && xml.name() == KEY_NAME) {
|
||||
xml.readNext();
|
||||
lastKey = xml.text().toString();
|
||||
if (rx.exactMatch(xml.text().toString())) {
|
||||
// Add the found file to the list
|
||||
_models.insert(QFileInfo(xml.text().toString()).baseName(),
|
||||
S3_URL + "/" + xml.text().toString());
|
||||
}
|
||||
}
|
||||
xml.readNext();
|
||||
}
|
||||
}
|
||||
xml.readNext();
|
||||
}
|
||||
|
||||
// Error handling
|
||||
if(xml.hasError()) {
|
||||
_models.clear();
|
||||
QMessageBox::critical(this,
|
||||
"ModelBrowser::ModelBrowser()",
|
||||
xml.errorString(),
|
||||
QMessageBox::Ok);
|
||||
return false;
|
||||
}
|
||||
|
||||
// If we didn't all the files, download the next ones
|
||||
if (truncated) {
|
||||
QUrl url(S3_URL);
|
||||
QUrlQuery query;
|
||||
|
||||
if (_type == Head) {
|
||||
query.addQueryItem(PREFIX_PARAMETER_NAME, HEAD_MODELS_LOCATION);
|
||||
} else if (_type == Skeleton) {
|
||||
query.addQueryItem(PREFIX_PARAMETER_NAME, SKELETON_MODELS_LOCATION);
|
||||
}
|
||||
query.addQueryItem(MARKER_PARAMETER_NAME, lastKey);
|
||||
url.setQuery(query);
|
||||
|
||||
delete _downloader;
|
||||
_downloader = new FileDownloader(url);
|
||||
connect(_downloader, SIGNAL(done(QNetworkReply::NetworkError)), SLOT(downloadFinished()));
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
62
interface/src/ModelBrowser.h
Normal file
62
interface/src/ModelBrowser.h
Normal file
|
@ -0,0 +1,62 @@
|
|||
//
|
||||
// ModelBrowser.h
|
||||
// hifi
|
||||
//
|
||||
// Created by Clement on 3/17/14.
|
||||
// Copyright (c) 2014 HighFidelity, Inc. All rights reserved.
|
||||
//
|
||||
|
||||
#ifndef __hifi__ModelBrowser__
|
||||
#define __hifi__ModelBrowser__
|
||||
|
||||
#include <FileDownloader.h>
|
||||
|
||||
#include <QDialog>
|
||||
#include <QListView>
|
||||
|
||||
static const QString S3_URL = "http://highfidelity-public.s3-us-west-1.amazonaws.com";
|
||||
static const QString HEAD_MODELS_LOCATION = "models/heads/";
|
||||
static const QString SKELETON_MODELS_LOCATION = "models/skeletons/";
|
||||
|
||||
enum ModelType {
|
||||
Head,
|
||||
Skeleton
|
||||
};
|
||||
|
||||
class ModelBrowser : public QWidget {
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
ModelBrowser(ModelType modelType, QWidget* parent = NULL);
|
||||
~ModelBrowser();
|
||||
|
||||
signals:
|
||||
void selected(QString);
|
||||
|
||||
public slots:
|
||||
void browse();
|
||||
|
||||
private slots:
|
||||
void downloadFinished();
|
||||
|
||||
private:
|
||||
ModelType _type;
|
||||
FileDownloader* _downloader;
|
||||
QHash<QString, QString> _models;
|
||||
|
||||
bool parseXML(QByteArray xmlFile);
|
||||
};
|
||||
|
||||
|
||||
|
||||
class ListView : public QListView {
|
||||
Q_OBJECT
|
||||
public:
|
||||
ListView(QWidget* parent) : QListView(parent) {}
|
||||
public slots:
|
||||
void keyboardSearch(const QString& text) {
|
||||
QAbstractItemView::keyboardSearch(text);
|
||||
}
|
||||
};
|
||||
|
||||
#endif /* defined(__hifi__ModelBrowser__) */
|
|
@ -14,6 +14,7 @@
|
|||
#include <QtNetwork/QNetworkRequest>
|
||||
#include <QtNetwork/QNetworkReply>
|
||||
|
||||
#include <AudioRingBuffer.h>
|
||||
#include <AvatarData.h>
|
||||
#include <NodeList.h>
|
||||
#include <PacketHeaders.h>
|
||||
|
@ -52,7 +53,9 @@ ScriptEngine::ScriptEngine(const QString& scriptContents, bool wantMenuItems, co
|
|||
_avatarIdentityTimer(NULL),
|
||||
_avatarBillboardTimer(NULL),
|
||||
_timerFunctionMap(),
|
||||
_avatarAudioBuffer(NULL),
|
||||
_isListeningToAudioStream(false),
|
||||
_avatarSound(NULL),
|
||||
_numAvatarSoundSentBytes(0),
|
||||
_controllerScriptingInterface(controllerScriptingInterface),
|
||||
_avatarData(NULL),
|
||||
_wantMenuItems(wantMenuItems),
|
||||
|
@ -260,27 +263,55 @@ void ScriptEngine::run() {
|
|||
}
|
||||
|
||||
if (_isAvatar && _avatarData) {
|
||||
|
||||
const int SCRIPT_AUDIO_BUFFER_SAMPLES = floor(((SCRIPT_DATA_CALLBACK_USECS * SAMPLE_RATE) / (1000 * 1000)) + 0.5);
|
||||
const int SCRIPT_AUDIO_BUFFER_BYTES = SCRIPT_AUDIO_BUFFER_SAMPLES * sizeof(int16_t);
|
||||
|
||||
QByteArray avatarPacket = byteArrayWithPopulatedHeader(PacketTypeAvatarData);
|
||||
avatarPacket.append(_avatarData->toByteArray());
|
||||
|
||||
nodeList->broadcastToNodes(avatarPacket, NodeSet() << NodeType::AvatarMixer);
|
||||
|
||||
if (_avatarAudioBuffer && _numAvatarAudioBufferSamples > 0) {
|
||||
// if have an avatar audio stream then send it out to our audio-mixer
|
||||
|
||||
if (_isListeningToAudioStream || _avatarSound) {
|
||||
// if we have an avatar audio stream then send it out to our audio-mixer
|
||||
bool silentFrame = true;
|
||||
|
||||
// check if the all of the _numAvatarAudioBufferSamples to be sent are silence
|
||||
for (int i = 0; i < _numAvatarAudioBufferSamples; ++i) {
|
||||
if (_avatarAudioBuffer[i] != 0) {
|
||||
silentFrame = false;
|
||||
break;
|
||||
int16_t numAvailableSamples = SCRIPT_AUDIO_BUFFER_SAMPLES;
|
||||
const int16_t* nextSoundOutput = NULL;
|
||||
|
||||
if (_avatarSound) {
|
||||
|
||||
const QByteArray& soundByteArray = _avatarSound->getByteArray();
|
||||
nextSoundOutput = reinterpret_cast<const int16_t*>(soundByteArray.data()
|
||||
+ _numAvatarSoundSentBytes);
|
||||
|
||||
int numAvailableBytes = (soundByteArray.size() - _numAvatarSoundSentBytes) > SCRIPT_AUDIO_BUFFER_BYTES
|
||||
? SCRIPT_AUDIO_BUFFER_BYTES
|
||||
: soundByteArray.size() - _numAvatarSoundSentBytes;
|
||||
numAvailableSamples = numAvailableBytes / sizeof(int16_t);
|
||||
|
||||
|
||||
// check if the all of the _numAvatarAudioBufferSamples to be sent are silence
|
||||
for (int i = 0; i < numAvailableSamples; ++i) {
|
||||
if (nextSoundOutput[i] != 0) {
|
||||
silentFrame = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
_numAvatarSoundSentBytes += numAvailableBytes;
|
||||
if (_numAvatarSoundSentBytes == soundByteArray.size()) {
|
||||
// we're done with this sound object - so set our pointer back to NULL
|
||||
// and our sent bytes back to zero
|
||||
_avatarSound = NULL;
|
||||
_numAvatarSoundSentBytes = 0;
|
||||
}
|
||||
}
|
||||
|
||||
QByteArray audioPacket = byteArrayWithPopulatedHeader(silentFrame
|
||||
? PacketTypeSilentAudioFrame
|
||||
: PacketTypeMicrophoneAudioNoEcho);
|
||||
|
||||
QDataStream packetStream(&audioPacket, QIODevice::Append);
|
||||
|
||||
// use the orientation and position of this avatar for the source of this audio
|
||||
|
@ -289,13 +320,17 @@ void ScriptEngine::run() {
|
|||
packetStream.writeRawData(reinterpret_cast<const char*>(&headOrientation), sizeof(glm::quat));
|
||||
|
||||
if (silentFrame) {
|
||||
if (!_isListeningToAudioStream) {
|
||||
// if we have a silent frame and we're not listening then just send nothing and break out of here
|
||||
break;
|
||||
}
|
||||
|
||||
// write the number of silent samples so the audio-mixer can uphold timing
|
||||
int16_t numSilentSamples = _numAvatarAudioBufferSamples;
|
||||
packetStream.writeRawData(reinterpret_cast<const char*>(&numSilentSamples), sizeof(int16_t));
|
||||
} else {
|
||||
packetStream.writeRawData(reinterpret_cast<const char*>(&SCRIPT_AUDIO_BUFFER_SAMPLES), sizeof(int16_t));
|
||||
} else if (nextSoundOutput) {
|
||||
// write the raw audio data
|
||||
packetStream.writeRawData(reinterpret_cast<const char*>(_avatarAudioBuffer),
|
||||
_numAvatarAudioBufferSamples * sizeof(int16_t));
|
||||
packetStream.writeRawData(reinterpret_cast<const char*>(nextSoundOutput),
|
||||
numAvailableSamples * sizeof(int16_t));
|
||||
}
|
||||
|
||||
nodeList->broadcastToNodes(audioPacket, NodeSet() << NodeType::AudioMixer);
|
||||
|
@ -303,7 +338,7 @@ void ScriptEngine::run() {
|
|||
}
|
||||
|
||||
qint64 now = usecTimestampNow();
|
||||
float deltaTime = (float)(now - lastUpdate)/(float)USECS_PER_SECOND;
|
||||
float deltaTime = (float) (now - lastUpdate) / (float) USECS_PER_SECOND;
|
||||
emit update(deltaTime);
|
||||
lastUpdate = now;
|
||||
|
||||
|
|
|
@ -56,10 +56,11 @@ public:
|
|||
|
||||
void setAvatarData(AvatarData* avatarData, const QString& objectName);
|
||||
|
||||
void setAvatarAudioBuffer(int16_t* avatarAudioBuffer) { _avatarAudioBuffer = avatarAudioBuffer; }
|
||||
bool sendsAvatarAudioStream() const { return (bool) _avatarAudioBuffer; }
|
||||
void setNumAvatarAudioBufferSamples(int numAvatarAudioBufferSamples)
|
||||
{ _numAvatarAudioBufferSamples = numAvatarAudioBufferSamples; }
|
||||
bool isListeningToAudioStream() const { return _isListeningToAudioStream; }
|
||||
void setIsListeningToAudioStream(bool isListeningToAudioStream) { _isListeningToAudioStream = isListeningToAudioStream; }
|
||||
|
||||
void setAvatarSound(Sound* avatarSound) { _avatarSound = avatarSound; }
|
||||
bool isPlayingAvatarSound() const { return _avatarSound != NULL; }
|
||||
|
||||
void init();
|
||||
void run(); /// runs continuously until Agent.stop() is called
|
||||
|
@ -91,8 +92,9 @@ protected:
|
|||
QTimer* _avatarIdentityTimer;
|
||||
QTimer* _avatarBillboardTimer;
|
||||
QHash<QTimer*, QScriptValue> _timerFunctionMap;
|
||||
int16_t* _avatarAudioBuffer;
|
||||
int _numAvatarAudioBufferSamples;
|
||||
bool _isListeningToAudioStream;
|
||||
Sound* _avatarSound;
|
||||
int _numAvatarSoundSentBytes;
|
||||
|
||||
private:
|
||||
void sendAvatarIdentityPacket();
|
||||
|
|
65
libraries/shared/src/FileDownloader.cpp
Normal file
65
libraries/shared/src/FileDownloader.cpp
Normal file
|
@ -0,0 +1,65 @@
|
|||
//
|
||||
// FileDownloader.cpp
|
||||
// hifi
|
||||
//
|
||||
// Created by Clement Brisset on 3/14/14.
|
||||
// Copyright (c) 2014 HighFidelity, Inc. All rights reserved.
|
||||
//
|
||||
//
|
||||
|
||||
#include <QUrl>
|
||||
#include <QNetworkRequest>
|
||||
#include <QEventLoop>
|
||||
#include <QTimer>
|
||||
|
||||
#include "FileDownloader.h"
|
||||
|
||||
FileDownloader::FileDownloader(const QUrl dataURL, QObject* parent) :
|
||||
QObject(parent),
|
||||
_done(false)
|
||||
{
|
||||
connect(&_networkAccessManager, SIGNAL(finished(QNetworkReply*)), this, SLOT(processReply(QNetworkReply*)));
|
||||
|
||||
QNetworkRequest request(dataURL);
|
||||
_networkAccessManager.get(request);
|
||||
}
|
||||
|
||||
void FileDownloader::processReply(QNetworkReply *reply) {
|
||||
if (reply->error() == QNetworkReply::NoError) {
|
||||
_downloadedData = reply->readAll();
|
||||
}
|
||||
|
||||
reply->deleteLater();
|
||||
_done = true;
|
||||
emit done(reply->error());
|
||||
}
|
||||
|
||||
void FileDownloader::waitForFile(int timeout) {
|
||||
QTimer timer;
|
||||
QEventLoop loop;
|
||||
connect(&timer, SIGNAL(timeout()), &loop, SLOT(quit()));
|
||||
connect(this, SIGNAL(done(QNetworkReply::NetworkError)), &loop, SLOT(quit()));
|
||||
|
||||
if (!_done) {
|
||||
if (timeout > 0) {
|
||||
timer.start(timeout);
|
||||
}
|
||||
loop.exec();
|
||||
}
|
||||
}
|
||||
|
||||
QByteArray FileDownloader::download(const QUrl dataURL, int timeout) {
|
||||
QTimer timer;
|
||||
QEventLoop loop;
|
||||
connect(&timer, SIGNAL(timeout()), &loop, SLOT(quit));
|
||||
|
||||
FileDownloader downloader(dataURL);
|
||||
connect(&downloader, SIGNAL(done(QNetworkReply::NetworkError)), &loop, SLOT(quit()));
|
||||
|
||||
if (timeout > 0) {
|
||||
timer.start(timeout);
|
||||
}
|
||||
loop.exec();
|
||||
|
||||
return downloader.getData();
|
||||
}
|
44
libraries/shared/src/FileDownloader.h
Normal file
44
libraries/shared/src/FileDownloader.h
Normal file
|
@ -0,0 +1,44 @@
|
|||
//
|
||||
// FileDownloader.h
|
||||
// hifi
|
||||
//
|
||||
// Created by Clement Brisset on 3/14/14.
|
||||
// Copyright (c) 2014 HighFidelity, Inc. All rights reserved.
|
||||
//
|
||||
//
|
||||
|
||||
#ifndef __hifi__FileDownloader__
|
||||
#define __hifi__FileDownloader__
|
||||
|
||||
#include <QObject>
|
||||
#include <QNetworkAccessManager>
|
||||
#include <QNetworkReply>
|
||||
|
||||
class FileDownloader : public QObject {
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
FileDownloader(const QUrl dataURL, QObject* parent = NULL);
|
||||
|
||||
void waitForFile(int timeout = 0);
|
||||
|
||||
QByteArray getData() const { return _downloadedData; }
|
||||
bool done() { return _done; }
|
||||
|
||||
static QByteArray download(const QUrl dataURL, int timeout = 0);
|
||||
|
||||
signals:
|
||||
void done(QNetworkReply::NetworkError);
|
||||
|
||||
private slots:
|
||||
void processReply(QNetworkReply* reply);
|
||||
|
||||
private:
|
||||
QNetworkAccessManager _networkAccessManager;
|
||||
QByteArray _downloadedData;
|
||||
|
||||
bool _done;
|
||||
};
|
||||
|
||||
|
||||
#endif /* defined(__hifi__FileDownloader__) */
|
Loading…
Reference in a new issue