Merge pull request #3308 from Atlante45/record_feature

Record feature Scripted UI
This commit is contained in:
AndrewMeadows 2014-08-21 09:56:02 -07:00
commit ff9f079704
7 changed files with 331 additions and 13 deletions

203
examples/Recorder.js Normal file
View file

@ -0,0 +1,203 @@
//
// Recorder.js
// examples
//
// Created by Clément Brisset on 8/20/14.
// Copyright 2014 High Fidelity, Inc.
//
// Distributed under the Apache License, Version 2.0.
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
//
Script.include("toolBars.js");
var recordingFile = "recording.rec";
var windowDimensions = Controller.getViewportDimensions();
var TOOL_ICON_URL = "http://s3-us-west-1.amazonaws.com/highfidelity-public/images/tools/";
var ALPHA_ON = 1.0;
var ALPHA_OFF = 0.7;
var COLOR_ON = { red: 128, green: 0, blue: 0 };
var COLOR_OFF = { red: 128, green: 128, blue: 128 };
Tool.IMAGE_WIDTH *= 0.7;
Tool.IMAGE_HEIGHT *= 0.7;
var toolBar = null;
var recordIcon;
var playIcon;
var saveIcon;
var loadIcon;
setupToolBar();
var timer = null;
setupTimer();
function setupToolBar() {
if (toolBar != null) {
print("Multiple calls to Recorder.js:setupToolBar()");
return;
}
toolBar = new ToolBar(0, 0, ToolBar.HORIZONTAL);
toolBar.setBack(COLOR_OFF, ALPHA_OFF);
recordIcon = toolBar.addTool({
imageURL: TOOL_ICON_URL + "record.svg",
width: Tool.IMAGE_WIDTH,
height: Tool.IMAGE_HEIGHT,
alpha: ALPHA_ON,
visible: true
}, false);
playIcon = toolBar.addTool({
imageURL: TOOL_ICON_URL + "play.svg",
width: Tool.IMAGE_WIDTH,
height: Tool.IMAGE_HEIGHT,
alpha: ALPHA_ON,
visible: true
}, false, false);
saveIcon = toolBar.addTool({
imageURL: TOOL_ICON_URL + "save.svg",
width: Tool.IMAGE_WIDTH,
height: Tool.IMAGE_HEIGHT,
alpha: ALPHA_ON,
visible: true
}, false, false);
loadIcon = toolBar.addTool({
imageURL: TOOL_ICON_URL + "load.svg",
width: Tool.IMAGE_WIDTH,
height: Tool.IMAGE_HEIGHT,
alpha: ALPHA_ON,
visible: true
}, false, false);
}
function setupTimer() {
timer = Overlays.addOverlay("text", {
font: { size: 20 },
text: (0.00).toFixed(3),
backgroundColor: COLOR_OFF,
x: 0, y: 0,
width: 100,
height: 100,
alpha: 1.0,
visible: true
});
}
function updateTimer() {
var text = "";
if (MyAvatar.isRecording()) {
text = formatTime(MyAvatar.recorderElapsed())
} else {
text = formatTime(MyAvatar.playerElapsed()) + " / " +
formatTime(MyAvatar.playerLength());
}
Overlays.editOverlay(timer, {
text: text
})
}
function formatTime(time) {
var MIN_PER_HOUR = 60;
var SEC_PER_MIN = 60;
var MSEC_PER_SEC = 1000;
var hours = Math.floor(time / (MSEC_PER_SEC * SEC_PER_MIN * MIN_PER_HOUR));
time -= hours * (MSEC_PER_SEC * SEC_PER_MIN * MIN_PER_HOUR);
var minutes = Math.floor(time / (MSEC_PER_SEC * SEC_PER_MIN));
time -= minutes * (MSEC_PER_SEC * SEC_PER_MIN);
var seconds = Math.floor(time / MSEC_PER_SEC);
seconds = time / MSEC_PER_SEC;
var text = "";
text += (hours > 0) ? hours + ":" :
"";
text += (minutes > 0) ? ((minutes < 10 && text != "") ? "0" : "") + minutes + ":" :
"";
text += ((seconds < 10 && text != "") ? "0" : "") + seconds.toFixed(3);
return text;
}
function moveUI() {
var relative = { x: 30, y: 90 };
toolBar.move(relative.x,
windowDimensions.y - relative.y);
Overlays.editOverlay(timer, {
x: relative.x - 10,
y: windowDimensions.y - relative.y - 35,
width: 0,
height: 0
});
}
function mousePressEvent(event) {
clickedOverlay = Overlays.getOverlayAtPoint({ x: event.x, y: event.y });
if (recordIcon === toolBar.clicked(clickedOverlay)) {
if (!MyAvatar.isRecording()) {
MyAvatar.startRecording();
toolBar.setBack(COLOR_ON, ALPHA_ON);
} else {
MyAvatar.stopRecording();
MyAvatar.loadLastRecording();
toolBar.setBack(COLOR_OFF, ALPHA_OFF);
}
} else if (playIcon === toolBar.clicked(clickedOverlay)) {
if (!MyAvatar.isRecording()) {
if (MyAvatar.isPlaying()) {
MyAvatar.stopPlaying();
} else {
MyAvatar.startPlaying();
}
}
} else if (saveIcon === toolBar.clicked(clickedOverlay)) {
if (!MyAvatar.isRecording()) {
recordingFile = Window.save("Save recording to file", ".", "*.rec");
MyAvatar.saveRecording(recordingFile);
}
} else if (loadIcon === toolBar.clicked(clickedOverlay)) {
if (!MyAvatar.isRecording()) {
recordingFile = Window.browse("Load recorcding from file", ".", "*.rec");
MyAvatar.loadRecording(recordingFile);
}
} else {
}
}
function update() {
var newDimensions = Controller.getViewportDimensions();
if (windowDimensions.x != newDimensions.x ||
windowDimensions.y != newDimensions.y) {
windowDimensions = newDimensions;
moveUI();
}
updateTimer();
}
function scriptEnding() {
if (MyAvatar.isRecording()) {
MyAvatar.stopRecording();
}
if (MyAvatar.isPlaying()) {
MyAvatar.stopPlaying();
}
toolBar.cleanup();
Overlays.deleteOverlay(timer);
}
Controller.mousePressEvent.connect(mousePressEvent);
Script.update.connect(update);
Script.scriptEnding.connect(scriptEnding);
// Should be called last to put everything into position
moveUI();

View file

@ -132,20 +132,34 @@ ToolBar = function(x, y, direction) {
this.y = y;
this.width = 0;
this.height = 0;
this.back = this.back = Overlays.addOverlay("text", {
backgroundColor: { red: 255, green: 255, blue: 255 },
x: this.x,
y: this.y,
width: this.width,
height: this.height,
alpha: 1.0,
visible: false
});
this.addTool = function(properties, selectable, selected) {
if (direction == ToolBar.HORIZONTAL) {
properties.x = this.x + this.width;
properties.y = this.y;
this.width += properties.width + ToolBar.SPACING;
this.height += Math.max(properties.height, this.height);
this.height = Math.max(properties.height, this.height);
} else {
properties.x = this.x;
properties.y = this.y + this.height;
this.width = Math.max(properties.width, this.width);
this.height += properties.height + ToolBar.SPACING;
}
if (this.back != null) {
Overlays.editOverlay(this.back, {
width: this.width + 2 * ToolBar.SPACING,
height: this.height + 2 * ToolBar.SPACING
});
}
this.tools[this.tools.length] = new Tool(properties, selectable, selected);
return ((this.tools.length) - 1);
@ -159,18 +173,48 @@ ToolBar = function(x, y, direction) {
for(var tool in this.tools) {
this.tools[tool].move(this.tools[tool].x() + dx, this.tools[tool].y() + dy);
}
if (this.back != null) {
Overlays.editOverlay(this.back, {
x: x - ToolBar.SPACING,
y: y - ToolBar.SPACING
});
}
}
this.setAlpha = function(alpha) {
for(var tool in this.tools) {
this.setAlpha = function(alpha, tool) {
if(typeof(tool) === 'undefined') {
for(var tool in this.tools) {
this.tools[tool].setAlpha(alpha);
}
if (this.back != null) {
Overlays.editOverlay(this.back, { alpha: alpha});
}
} else {
this.tools[tool].setAlpha(alpha);
}
}
this.setBack = function(color, alpha) {
if (color == null) {
Overlays.editOverlay(this.back, {
visible: false
});
} else {
Overlays.editOverlay(this.back, {
visible: true,
backgroundColor: color,
alpha: alpha
})
}
}
this.show = function(doShow) {
for(var tool in this.tools) {
this.tools[tool].show(doShow);
}
if (this.back != null) {
Overlays.editOverlay(this.back, { visible: doShow});
}
}
this.clicked = function(clickedOverlay) {
@ -200,6 +244,11 @@ ToolBar = function(x, y, direction) {
delete this.tools[tool];
}
if (this.back != null) {
Overlays.deleteOverlay(this.back);
this.back = null;
}
this.tools = [];
this.x = x;
this.y = y;

View file

@ -223,7 +223,7 @@ void Player::startPlaying() {
_audioThread = new QThread();
_options.setPosition(_avatar->getPosition());
_options.setOrientation(_avatar->getOrientation());
_injector.reset(new AudioInjector(_recording->getAudio(), _options));
_injector.reset(new AudioInjector(_recording->getAudio(), _options), &QObject::deleteLater);
_injector->moveToThread(_audioThread);
_audioThread->start();
QMetaObject::invokeMethod(_injector.data(), "injectAudio", Qt::QueuedConnection);
@ -317,13 +317,18 @@ bool Player::computeCurrentFrame() {
}
void writeRecordingToFile(RecordingPointer recording, QString filename) {
qDebug() << "Writing recording to " << filename;
if (!recording || recording->getFrameNumber() < 1) {
qDebug() << "Can't save empty recording";
return;
}
qDebug() << "Writing recording to " << filename << ".";
QElapsedTimer timer;
QFile file(filename);
if (!file.open(QIODevice::WriteOnly)){
return;
}
qDebug() << file.fileName();
timer.start();
QDataStream fileStream(&file);
@ -443,20 +448,21 @@ void writeRecordingToFile(RecordingPointer recording, QString filename) {
fileStream << recording->_audio->getByteArray();
qDebug() << "Wrote " << file.size() << " bytes in " << timer.elapsed();
qDebug() << "Wrote " << file.size() << " bytes in " << timer.elapsed() << " ms.";
}
RecordingPointer readRecordingFromFile(RecordingPointer recording, QString filename) {
qDebug() << "Reading recording from " << filename;
qDebug() << "Reading recording from " << filename << ".";
if (!recording) {
recording.reset(new Recording());
}
QElapsedTimer timer;
QFile file(filename);
if (!file.open(QIODevice::ReadOnly)){
return recording;
}
timer.start();
QDataStream fileStream(&file);
fileStream >> recording->_timestamps;
@ -557,7 +563,7 @@ RecordingPointer readRecordingFromFile(RecordingPointer recording, QString filen
recording->addAudioPacket(audioArray);
qDebug() << "Read " << file.size() << " bytes";
qDebug() << "Read " << file.size() << " bytes in " << timer.elapsed() << " ms.";
return recording;
}

View file

@ -137,6 +137,8 @@ public:
bool isPlaying() const;
qint64 elapsed() const;
RecordingPointer getRecording() const { return _recording; }
// Those should only be called if isPlaying() returns true
glm::quat getHeadRotation();
float getLeanSideways();

View file

@ -509,6 +509,9 @@ bool MyAvatar::setJointReferential(int id, int jointIndex) {
}
bool MyAvatar::isRecording() {
if (!_recorder) {
return false;
}
if (QThread::currentThread() != thread()) {
bool result;
QMetaObject::invokeMethod(this, "isRecording", Qt::BlockingQueuedConnection,
@ -518,6 +521,19 @@ bool MyAvatar::isRecording() {
return _recorder && _recorder->isRecording();
}
qint64 MyAvatar::recorderElapsed() {
if (!_recorder) {
return 0;
}
if (QThread::currentThread() != thread()) {
qint64 result;
QMetaObject::invokeMethod(this, "recorderElapsed", Qt::BlockingQueuedConnection,
Q_RETURN_ARG(qint64, result));
return result;
}
return _recorder->elapsed();
}
void MyAvatar::startRecording() {
if (QThread::currentThread() != thread()) {
QMetaObject::invokeMethod(this, "startRecording", Qt::BlockingQueuedConnection);
@ -528,9 +544,13 @@ void MyAvatar::startRecording() {
}
Application::getInstance()->getAudio()->setRecorder(_recorder);
_recorder->startRecording();
}
void MyAvatar::stopRecording() {
if (!_recorder) {
return;
}
if (QThread::currentThread() != thread()) {
QMetaObject::invokeMethod(this, "stopRecording", Qt::BlockingQueuedConnection);
return;
@ -541,6 +561,10 @@ void MyAvatar::stopRecording() {
}
void MyAvatar::saveRecording(QString filename) {
if (!_recorder) {
qDebug() << "There is no recording to save";
return;
}
if (QThread::currentThread() != thread()) {
QMetaObject::invokeMethod(this, "saveRecording", Qt::BlockingQueuedConnection,
Q_ARG(QString, filename));
@ -552,6 +576,9 @@ void MyAvatar::saveRecording(QString filename) {
}
bool MyAvatar::isPlaying() {
if (!_player) {
return false;
}
if (QThread::currentThread() != thread()) {
bool result;
QMetaObject::invokeMethod(this, "isPlaying", Qt::BlockingQueuedConnection,
@ -561,6 +588,32 @@ bool MyAvatar::isPlaying() {
return _player && _player->isPlaying();
}
qint64 MyAvatar::playerElapsed() {
if (!_player) {
return 0;
}
if (QThread::currentThread() != thread()) {
qint64 result;
QMetaObject::invokeMethod(this, "playerElapsed", Qt::BlockingQueuedConnection,
Q_RETURN_ARG(qint64, result));
return result;
}
return _player->elapsed();
}
qint64 MyAvatar::playerLength() {
if (!_player) {
return 0;
}
if (QThread::currentThread() != thread()) {
qint64 result;
QMetaObject::invokeMethod(this, "playerLength", Qt::BlockingQueuedConnection,
Q_RETURN_ARG(qint64, result));
return result;
}
return _player->getRecording()->getLength();
}
void MyAvatar::loadRecording(QString filename) {
if (QThread::currentThread() != thread()) {
QMetaObject::invokeMethod(this, "loadRecording", Qt::BlockingQueuedConnection,
@ -580,6 +633,7 @@ void MyAvatar::loadLastRecording() {
return;
}
if (!_recorder) {
qDebug() << "There is no recording to load";
return;
}
if (!_player) {
@ -603,6 +657,9 @@ void MyAvatar::startPlaying() {
}
void MyAvatar::stopPlaying() {
if (!_player) {
return;
}
if (QThread::currentThread() != thread()) {
QMetaObject::invokeMethod(this, "stopPlaying", Qt::BlockingQueuedConnection);
return;

View file

@ -170,11 +170,14 @@ public slots:
bool setJointReferential(int id, int jointIndex);
bool isRecording();
qint64 recorderElapsed();
void startRecording();
void stopRecording();
void saveRecording(QString filename);
bool isPlaying();
qint64 playerElapsed();
qint64 playerLength();
void loadRecording(QString filename);
void loadLastRecording();
void startPlaying();

View file

@ -295,7 +295,6 @@ QScriptValue WindowScriptingInterface::showBrowse(const QString& title, const QS
// filename if the directory is valid.
QString path = "";
QFileInfo fileInfo = QFileInfo(directory);
qDebug() << "File: " << directory << fileInfo.isFile();
if (fileInfo.isDir()) {
fileInfo.setFile(directory, "__HIFI_INVALID_FILE__");
path = fileInfo.filePath();
@ -303,7 +302,6 @@ QScriptValue WindowScriptingInterface::showBrowse(const QString& title, const QS
QFileDialog fileDialog(Application::getInstance()->getWindow(), title, path, nameFilter);
fileDialog.setAcceptMode(acceptMode);
qDebug() << "Opening!";
QUrl fileUrl(directory);
if (acceptMode == QFileDialog::AcceptSave) {
fileDialog.setFileMode(QFileDialog::Directory);