mumble-voip_mumble/src/mumble/VoiceRecorderDialog.cpp

283 lines
8.0 KiB
C++

// Copyright 2010-2023 The Mumble Developers. All rights reserved.
// Use of this source code is governed by a BSD-style license
// that can be found in the LICENSE file at the root of the
// Mumble source tree or at <https://www.mumble.info/LICENSE>.
#include "VoiceRecorderDialog.h"
#include "AudioOutput.h"
#include "Log.h"
#include "ServerHandler.h"
#include "VoiceRecorder.h"
#include "Global.h"
#include <QtGui/QCloseEvent>
#include <QtWidgets/QFileDialog>
#include <QtWidgets/QMessageBox>
VoiceRecorderDialog::VoiceRecorderDialog(QWidget *p) : QDialog(p), qtTimer(new QTimer(this)) {
qtTimer->setObjectName(QLatin1String("qtTimer"));
qtTimer->setInterval(200);
setupUi(this);
qleTargetDirectory->setText(Global::get().s.qsRecordingPath);
qleFilename->setText(Global::get().s.qsRecordingFile);
qrbDownmix->setChecked(Global::get().s.rmRecordingMode == Settings::RecordingMixdown);
qrbMultichannel->setChecked(Global::get().s.rmRecordingMode == Settings::RecordingMultichannel);
QString qsTooltip = QString::fromLatin1("%1"
"<table>"
" <tr>"
" <td width=\"25%\">%user</td>"
" <td>%2</td>"
" </tr>"
" <tr>"
" <td>%date</td>"
" <td>%3</td>"
" </tr>"
" <tr>"
" <td>%time</td>"
" <td>%4</td>"
" </tr>"
" <tr>"
" <td>%host</td>"
" <td>%5</td>"
" </tr>"
"</table>")
.arg(tr("Valid variables are:"))
.arg(tr("Inserts the user's name"))
.arg(tr("Inserts the current date"))
.arg(tr("Inserts the current time"))
.arg(tr("Inserts the hostname"));
qleTargetDirectory->setToolTip(qsTooltip);
qleFilename->setToolTip(qsTooltip);
// Populate available codecs
Q_ASSERT(VoiceRecorderFormat::kEnd != 0);
for (int fm = 0; fm < VoiceRecorderFormat::kEnd; fm++) {
qcbFormat->addItem(VoiceRecorderFormat::getFormatDescription(static_cast< VoiceRecorderFormat::Format >(fm)));
}
if (Global::get().s.iRecordingFormat < 0 || Global::get().s.iRecordingFormat > VoiceRecorderFormat::kEnd)
Global::get().s.iRecordingFormat = 0;
qcbFormat->setCurrentIndex(Global::get().s.iRecordingFormat);
}
VoiceRecorderDialog::~VoiceRecorderDialog() {
reset();
}
void VoiceRecorderDialog::closeEvent(QCloseEvent *evt) {
if (Global::get().sh) {
VoiceRecorderPtr recorder(Global::get().sh->recorder);
if (recorder && recorder->isRunning()) {
int ret = QMessageBox::warning(this, tr("Recorder still running"),
tr("Closing the recorder without stopping it will discard unwritten audio. "
"Do you really want to close the recorder?"),
QMessageBox::Yes | QMessageBox::No, QMessageBox::No);
if (ret == QMessageBox::No) {
evt->ignore();
return;
}
recorder->stop(true);
}
}
Global::get().s.qsRecordingPath = qleTargetDirectory->text();
Global::get().s.qsRecordingFile = qleFilename->text();
if (qrbDownmix->isChecked())
Global::get().s.rmRecordingMode = Settings::RecordingMixdown;
else
Global::get().s.rmRecordingMode = Settings::RecordingMultichannel;
int i = qcbFormat->currentIndex();
Global::get().s.iRecordingFormat = (i == -1) ? 0 : i;
reset();
evt->accept();
QDialog::closeEvent(evt);
}
void VoiceRecorderDialog::on_qpbStart_clicked() {
if (!Global::get().uiSession || !Global::get().sh) {
QMessageBox::critical(this, tr("Recorder"), tr("Unable to start recording. Not connected to a server."));
reset();
return;
}
if (Global::get().sh->m_version < Version::fromComponents(1, 2, 3)) {
QMessageBox::critical(this, tr("Recorder"),
tr("The server you are currently connected to is version 1.2.2 or older. "
"For privacy reasons, recording on servers of versions older than 1.2.3 "
"is not possible.\nPlease contact your server administrator for further "
"information."));
return;
}
if (Global::get().sh->recorder) {
QMessageBox::information(this, tr("Recorder"), tr("There is already a recorder active for this server."));
return;
}
// Check validity of input
int ifm = qcbFormat->currentIndex();
if (ifm == -1) {
QMessageBox::critical(this, tr("Recorder"), tr("Please select a recording format."));
return;
}
QString dstr = qleTargetDirectory->text();
if (dstr.isEmpty()) {
on_qpbTargetDirectoryBrowse_clicked();
dstr = qleTargetDirectory->text();
if (dstr.isEmpty())
return;
}
QDir dir(dstr);
QFileInfo fi(qleFilename->text());
QString basename(fi.baseName());
QString suffix(fi.completeSuffix());
if (suffix.isEmpty())
suffix = VoiceRecorderFormat::getFormatDefaultExtension(static_cast< VoiceRecorderFormat::Format >(ifm));
if (basename.isEmpty()) {
basename = QLatin1String("%user");
}
qleFilename->setText(basename);
AudioOutputPtr ao(Global::get().ao);
if (!ao)
return;
// Create the recorder
VoiceRecorder::Config config;
config.sampleRate = static_cast< int >(ao->getMixerFreq());
config.fileName = dir.absoluteFilePath(basename + QLatin1Char('.') + suffix);
config.mixDownMode = qrbDownmix->isChecked();
config.recordingFormat = static_cast< VoiceRecorderFormat::Format >(ifm);
if (config.sampleRate == 0) {
// If we don't catch this here, Mumble will crash because VoiceRecorder expects the sample rate to be non-zero
Global::get().l->log(Log::Warning,
tr("Unable to start recording - the audio output is miconfigured (0Hz sample rate)"));
// Close this dialog
reject();
return;
}
Global::get().sh->announceRecordingState(true);
Global::get().sh->recorder.reset(new VoiceRecorder(this, config));
VoiceRecorderPtr recorder(Global::get().sh->recorder);
// Wire it up
connect(&*recorder, SIGNAL(recording_started()), this, SLOT(onRecorderStarted()));
connect(&*recorder, SIGNAL(recording_stopped()), this, SLOT(onRecorderStopped()));
connect(&*recorder, SIGNAL(error(int, QString)), this, SLOT(onRecorderError(int, QString)));
recorder->start();
qpbStart->setDisabled(true);
qpbStop->setEnabled(true);
qpbStop->setFocus();
qgbMode->setDisabled(true);
qgbOutput->setDisabled(true);
}
void VoiceRecorderDialog::on_qpbStop_clicked() {
if (!Global::get().sh) {
reset();
return;
}
VoiceRecorderPtr recorder(Global::get().sh->recorder);
if (!recorder) {
reset();
return;
}
// Stop clock and recording
qtTimer->stop();
recorder->stop();
// Disable stop button to indicate we reacted
qpbStop->setDisabled(true);
qpbStop->setText(tr("Stopping"));
}
void VoiceRecorderDialog::on_qtTimer_timeout() {
if (!Global::get().sh) {
reset();
return;
}
if (!Global::get().uiSession) {
reset(false);
return;
}
VoiceRecorderPtr recorder(Global::get().sh->recorder);
if (!Global::get().sh->recorder) {
reset();
return;
}
const QTime elapsedTime = QTime(0, 0).addMSecs(static_cast< int >(recorder->getElapsedTime() / 1000));
qlTime->setText(elapsedTime.toString());
}
void VoiceRecorderDialog::on_qpbTargetDirectoryBrowse_clicked() {
QString dir = QFileDialog::getExistingDirectory(this, tr("Select target directory"), QString(),
QFileDialog::ShowDirsOnly | QFileDialog::DontResolveSymlinks);
if (!dir.isEmpty())
qleTargetDirectory->setText(dir);
}
void VoiceRecorderDialog::reset(bool resettimer) {
qtTimer->stop();
if (Global::get().sh) {
VoiceRecorderPtr recorder(Global::get().sh->recorder);
if (recorder) {
Global::get().sh->recorder.reset();
Global::get().sh->announceRecordingState(false);
}
}
qpbStart->setEnabled(true);
qpbStart->setFocus();
qpbStop->setDisabled(true);
qpbStop->setText(tr("S&top"));
qgbMode->setEnabled(true);
qgbOutput->setEnabled(true);
if (resettimer)
qlTime->setText(QLatin1String("00:00:00"));
}
void VoiceRecorderDialog::onRecorderStopped() {
reset(false);
}
void VoiceRecorderDialog::onRecorderStarted() {
qlTime->setText(QLatin1String("00:00:00"));
qtTimer->start();
}
void VoiceRecorderDialog::onRecorderError(int err, QString strerr) {
Q_UNUSED(err);
QMessageBox::critical(this, tr("Recorder"), strerr);
reset(false);
}