mirror of
https://github.com/lubosz/overte.git
synced 2025-04-23 20:34:07 +02:00
Merge pull request #3308 from Atlante45/record_feature
Record feature Scripted UI
This commit is contained in:
commit
ff9f079704
7 changed files with 331 additions and 13 deletions
203
examples/Recorder.js
Normal file
203
examples/Recorder.js
Normal 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();
|
||||
|
||||
|
|
@ -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;
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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);
|
||||
|
|
Loading…
Reference in a new issue