diff --git a/examples/Recorder.js b/examples/Recorder.js new file mode 100644 index 0000000000..9a4375ab4f --- /dev/null +++ b/examples/Recorder.js @@ -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(); + + diff --git a/examples/toolBars.js b/examples/toolBars.js index ede3b80062..064ae372fd 100644 --- a/examples/toolBars.js +++ b/examples/toolBars.js @@ -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; diff --git a/interface/src/Recorder.cpp b/interface/src/Recorder.cpp index 52d183332d..fb5c92c849 100644 --- a/interface/src/Recorder.cpp +++ b/interface/src/Recorder.cpp @@ -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; } diff --git a/interface/src/Recorder.h b/interface/src/Recorder.h index 9f7eb66ec6..2670d78365 100644 --- a/interface/src/Recorder.h +++ b/interface/src/Recorder.h @@ -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(); diff --git a/interface/src/avatar/MyAvatar.cpp b/interface/src/avatar/MyAvatar.cpp index b3805d15c7..1d271d5f40 100644 --- a/interface/src/avatar/MyAvatar.cpp +++ b/interface/src/avatar/MyAvatar.cpp @@ -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; diff --git a/interface/src/avatar/MyAvatar.h b/interface/src/avatar/MyAvatar.h index 42deafd0fa..37daab3fa8 100644 --- a/interface/src/avatar/MyAvatar.h +++ b/interface/src/avatar/MyAvatar.h @@ -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(); diff --git a/interface/src/scripting/WindowScriptingInterface.cpp b/interface/src/scripting/WindowScriptingInterface.cpp index 7cf37b4424..7a85fc7117 100644 --- a/interface/src/scripting/WindowScriptingInterface.cpp +++ b/interface/src/scripting/WindowScriptingInterface.cpp @@ -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);