7af2528bfd
- The majority of user interaction is now in the system tray icon. There's no longer a traditional user interface window. - Added a proper application version number, GPL and source code comments. - The application will now allow only one instance and will play the file requested in the arguments in the currently running instance. - Added mute control. - Settings are now saved in csv file format instead of idm. - Several bug fixes. - Seeker removed for now. (might but it back in the future)
451 lines
14 KiB
C++
451 lines
14 KiB
C++
#include "core.h"
|
|
|
|
// This file is part of JustAudio.
|
|
|
|
// JustAudio is free software: you can redistribute it and/or modify
|
|
// it under the terms of the GNU General Public License as published by
|
|
// the Free Software Foundation, either version 3 of the License, or
|
|
// (at your option) any later version.
|
|
|
|
// JustAudio is distributed in the hope that it will be useful,
|
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
// GNU General Public License for more details.
|
|
|
|
// You should have received a copy of the GNU General Public License
|
|
// along with BashWire under the GPL.txt file. If not, see
|
|
// <http://www.gnu.org/licenses/>.
|
|
|
|
Core::Core(QObject *parent) : QObject(parent)
|
|
{
|
|
// the Core class acts as a non-gui centralized class that house the majority
|
|
// of all functions the application needs to operate.
|
|
|
|
nextCheck = new QCheckBox(tr("Play files in directory"));
|
|
muteCheck = new QCheckBox(tr("Mute"));
|
|
volumeSlider = new QSlider(Qt::Horizontal);
|
|
currentFile = new QLabel(tr("Ready"));
|
|
audFile = new AudFile(this);
|
|
sysTray = 0;
|
|
|
|
// QMediaPlayer volume range 0-100.
|
|
|
|
volumeSlider->setMinimum(0);
|
|
volumeSlider->setMaximum(100);
|
|
volumeSlider->setTracking(true);
|
|
currentFile->setWordWrap(true);
|
|
|
|
QFile file(CONFIG_FILE, this);
|
|
|
|
bool ok = false;
|
|
|
|
if (file.open(QFile::ReadOnly))
|
|
{
|
|
// the CONFIG_FILE is formated in csv so all data is seperated by a ','
|
|
// the data will simply not load if the strings are not in exactly the
|
|
// same order as it was saved.
|
|
// data formats:
|
|
// VOLUME = numeric string
|
|
// PLAY_DIR_CHECK = string ("Y" or "N")
|
|
// MUTE = string ("Y" or "N")
|
|
|
|
QByteArray confData = file.readAll();
|
|
QList<QByteArray> split = confData.split(',');
|
|
|
|
if (split.size() == 3)
|
|
{
|
|
int volume = split[VOLUME].toInt(&ok);
|
|
|
|
if (ok)
|
|
{
|
|
volumeSlider->setValue(volume);
|
|
|
|
emit setVolume(volume);
|
|
|
|
if (split[PLAY_DIR_CHECK] == "Y") nextCheck->setChecked(true);
|
|
else nextCheck->setChecked(false);
|
|
|
|
if (split[MUTE] == "Y") muteCheck->setChecked(true);
|
|
else muteCheck->setChecked(false);
|
|
|
|
}
|
|
}
|
|
}
|
|
|
|
if (!ok)
|
|
{
|
|
// set default values on failure to read the CONFIG_FILE.
|
|
|
|
emit setVolume(50);
|
|
|
|
volumeSlider->setValue(50);
|
|
nextCheck->setChecked(false);
|
|
muteCheck->setChecked(false);
|
|
}
|
|
|
|
file.close();
|
|
|
|
// it's very important that the player stops playback before closing the application
|
|
// or it will cause a crash.
|
|
|
|
connect(audFile, SIGNAL(endOfPlayback()), this, SLOT(playBackFinished()));
|
|
connect(QApplication::instance(), SIGNAL(aboutToQuit()), this, SIGNAL(stop()));
|
|
}
|
|
|
|
Core::~Core()
|
|
{
|
|
audFile->close();
|
|
|
|
QFile file(CONFIG_FILE, this);
|
|
|
|
if (file.open(QFile::WriteOnly | QFile::Truncate))
|
|
{
|
|
// CONFIG_FILE format:
|
|
// [volume],[play_dir_check],[mute]
|
|
|
|
// data formats:
|
|
// VOLUME = numeric string
|
|
// PLAY_DIR_CHECK = string ("Y" or "N")
|
|
// MUTE = string ("Y" or "N")
|
|
|
|
file.write(QByteArray::number(volumeSlider->value()));
|
|
file.write(",");
|
|
|
|
if (nextCheck->isChecked()) file.write("Y");
|
|
else file.write("N");
|
|
|
|
file.write(",");
|
|
|
|
if (muteCheck->isChecked()) file.write("Y");
|
|
else file.write("N");
|
|
}
|
|
|
|
file.close();
|
|
}
|
|
|
|
void Core::setFileWatcher(QFileSystemWatcher *watcher)
|
|
{
|
|
connect(watcher, SIGNAL(fileChanged(QString)), this, SLOT(processfileChanged(QString)));
|
|
}
|
|
|
|
void Core::setPlayer(QMediaPlayer *player)
|
|
{
|
|
// the Core class does not run funtions directly on the QMediaPlayer
|
|
// class that lives in the main function so everything it needs to
|
|
// do are connected via slots and signals.
|
|
|
|
connect(this, SIGNAL(start()), player, SLOT(play()));
|
|
connect(this, SIGNAL(pause()), player, SLOT(pause()));
|
|
connect(this, SIGNAL(stop()), player, SLOT(stop()));
|
|
connect(this, SIGNAL(setMedia(QMediaContent,QIODevice*)), player, SLOT(setMedia(QMediaContent,QIODevice*)));
|
|
connect(this, SIGNAL(setVolume(int)), player, SLOT(setVolume(int)));
|
|
connect(this, SIGNAL(muteState(bool)), player, SLOT(setMuted(bool)));
|
|
connect(player, SIGNAL(stateChanged(QMediaPlayer::State)), this, SLOT(stateChanged(QMediaPlayer::State)));
|
|
}
|
|
|
|
void Core::setTrayIcon(QSystemTrayIcon *tray)
|
|
{
|
|
// must user interaction will be on the system tray icon for this app.
|
|
// this funtion sets up the context menu so the user can have access
|
|
// to controls like play, pause, next, previous, etc...
|
|
|
|
QMenu *contextMenu = new QMenu();
|
|
QWidget *volumeWidget = new QWidget();
|
|
QHBoxLayout *volumeLayout = new QHBoxLayout(volumeWidget);
|
|
QWidgetAction *nextCheckAction = new QWidgetAction(this);
|
|
QWidgetAction *muteAction = new QWidgetAction(this);
|
|
QWidgetAction *volumeAction = new QWidgetAction(this);
|
|
QWidgetAction *fileNameAction = new QWidgetAction(this);
|
|
QToolButton *volUpIcon = new QToolButton();
|
|
QToolButton *volDownIcon = new QToolButton();
|
|
|
|
fileNameAction->setDefaultWidget(currentFile);
|
|
contextMenu->addAction(fileNameAction);
|
|
contextMenu->addSeparator();
|
|
|
|
playAction = contextMenu->addAction(QIcon(":/png/play"), tr("Play"), this, SIGNAL(start()));
|
|
pauseAction = contextMenu->addAction(QIcon(":/png/pause"), tr("Pause"), this, SIGNAL(pause()));
|
|
stopAction = contextMenu->addAction(QIcon(":/png/stop"), tr("Stop"), this, SIGNAL(stop()));
|
|
sysTray = tray;
|
|
|
|
volUpIcon->setIcon(QIcon(":/png/volume_up"));
|
|
volDownIcon->setIcon(QIcon(":/png/volume_down"));
|
|
volumeLayout->addWidget(volDownIcon);
|
|
volumeLayout->addWidget(volumeSlider);
|
|
volumeLayout->addWidget(volUpIcon);
|
|
playAction->setEnabled(false); // play, pause, stop, previous and next are disabled initially.
|
|
pauseAction->setEnabled(false); // this will change when an audio file is opened
|
|
stopAction->setEnabled(false); // (see stateChanged())
|
|
volumeAction->setDefaultWidget(volumeWidget);
|
|
nextCheckAction->setDefaultWidget(nextCheck);
|
|
muteAction->setDefaultWidget(muteCheck);
|
|
contextMenu->addAction(QIcon(":/png/open"), tr("Open"), this, SLOT(openDialog()));
|
|
|
|
nextAction = contextMenu->addAction(QIcon(":/png/next"), tr("Next"), this, SLOT(nextFile()));
|
|
prevAction = contextMenu->addAction(QIcon(":/png/prev"), tr("Previous"), this, SLOT(prevFile()));
|
|
|
|
nextAction->setEnabled(false);
|
|
prevAction->setEnabled(false);
|
|
contextMenu->addSeparator();
|
|
contextMenu->addAction(nextCheckAction);
|
|
contextMenu->addAction(muteAction);
|
|
contextMenu->addSeparator();
|
|
contextMenu->addAction(volumeAction);
|
|
contextMenu->addSeparator();
|
|
contextMenu->addAction(tr("License"), this, SLOT(showLicense()));
|
|
contextMenu->addAction(tr("Exit"), QApplication::instance(), SLOT(quit()));
|
|
tray->setContextMenu(contextMenu);
|
|
|
|
connect(tray, SIGNAL(activated(QSystemTrayIcon::ActivationReason)), this, SLOT(trayClicked(QSystemTrayIcon::ActivationReason)));
|
|
connect(muteCheck, SIGNAL(clicked(bool)), this, SIGNAL(muteState(bool)));
|
|
connect(volumeSlider, SIGNAL(valueChanged(int)), this, SIGNAL(setVolume(int)));
|
|
connect(volUpIcon, SIGNAL(clicked()), this, SLOT(volumeUp()));
|
|
connect(volDownIcon, SIGNAL(clicked()), this, SLOT(volumeDown()));
|
|
connect(pauseAction, SIGNAL(triggered()), audFile, SLOT(userInterupt()));
|
|
connect(stopAction, SIGNAL(triggered()), audFile, SLOT(userInterupt()));
|
|
connect(playAction, SIGNAL(triggered()), audFile, SLOT(userResume()));
|
|
}
|
|
|
|
void Core::processfileChanged(const QString &path)
|
|
{
|
|
Q_UNUSED(path);
|
|
|
|
QFile file(PROCESS_FILE, this);
|
|
|
|
if (file.open(QFile::ReadOnly))
|
|
{
|
|
// expected data from the PROCESS_FILE should just be a directory path
|
|
// string to the audio file to be opened. play() will call out an error
|
|
// if it fails to open this file.
|
|
|
|
play(file.readAll().trimmed());
|
|
}
|
|
|
|
file.close();
|
|
}
|
|
|
|
void Core::play(const QString &path)
|
|
{
|
|
emit stop();
|
|
|
|
if (audFile->openFile(path))
|
|
{
|
|
QFileInfo info(path);
|
|
|
|
currentFile->setText(info.fileName());
|
|
|
|
if (sysTray)
|
|
{
|
|
sysTray->setToolTip(QApplication::applicationName() + " " + QApplication::applicationVersion() + " - " + info.fileName());
|
|
}
|
|
|
|
emit setMedia(0, audFile);
|
|
emit start();
|
|
}
|
|
else
|
|
{
|
|
QMessageBox box;
|
|
|
|
box.setText(tr("Failed to open file: ") + path);
|
|
box.setDetailedText(audFile->errorString());
|
|
box.exec();
|
|
}
|
|
}
|
|
|
|
void Core::nextFile()
|
|
{
|
|
fileDir('+');
|
|
}
|
|
|
|
void Core::prevFile()
|
|
{
|
|
fileDir('-');
|
|
}
|
|
|
|
void Core::fileDir(char direction)
|
|
{
|
|
// using the current file as a reference, this function will first find out the
|
|
// directory the file lives in and then use that directory to list all files within
|
|
// it. then using the current file as a reference again, it trys to find it in the
|
|
// list of files and then use that file position to find out the next or previous
|
|
// file in the directory to play.
|
|
|
|
// if the reference file no longer exist then it will attempt to play 1st file in the
|
|
// directory. if the directory no longer exist then nothing happens.
|
|
|
|
QFileInfo info(audFile->fileName());
|
|
QDir dir(info.path());
|
|
|
|
QStringList list = dir.entryList(audExtensions(), QDir::Files | QDir::Readable, QDir::Name | QDir::IgnoreCase);
|
|
int pos = list.indexOf(info.fileName());
|
|
|
|
if (!list.isEmpty())
|
|
{
|
|
if (direction == '+') pos++;
|
|
else if (direction == '-') pos--;
|
|
|
|
if (pos < 0) pos = 0;
|
|
|
|
if (pos < list.size())
|
|
{
|
|
play(info.path() + QDir::separator() + list[pos]);
|
|
}
|
|
}
|
|
}
|
|
|
|
void Core::openDialog()
|
|
{
|
|
QFileDialog fileDialog;
|
|
QString filterString = tr("Audio Files (");
|
|
QStringList list = audExtensions();
|
|
|
|
for (int i = 0; i < list.size(); ++i)
|
|
{
|
|
filterString.append(list[i] + " ");
|
|
}
|
|
|
|
filterString = filterString.trimmed();
|
|
|
|
filterString.append(")");
|
|
|
|
fileDialog.setFileMode(QFileDialog::ExistingFile);
|
|
fileDialog.setNameFilter(filterString);
|
|
fileDialog.setDirectory(QDir::homePath());
|
|
|
|
if (fileDialog.exec())
|
|
{
|
|
play(fileDialog.selectedFiles()[0]);
|
|
}
|
|
}
|
|
|
|
void Core::playBackFinished()
|
|
{
|
|
if (nextCheck->isChecked()) nextFile();
|
|
}
|
|
|
|
void Core::stateChanged(QMediaPlayer::State state)
|
|
{
|
|
mediaState = state;
|
|
|
|
switch(state)
|
|
{
|
|
case QMediaPlayer::PlayingState:
|
|
{
|
|
playAction->setEnabled(false);
|
|
pauseAction->setEnabled(true);
|
|
stopAction->setEnabled(true);
|
|
prevAction->setEnabled(true);
|
|
nextAction->setEnabled(true);
|
|
|
|
break;
|
|
}
|
|
case QMediaPlayer::StoppedState:
|
|
{
|
|
playAction->setEnabled(true);
|
|
pauseAction->setEnabled(false);
|
|
stopAction->setEnabled(false);
|
|
|
|
break;
|
|
}
|
|
case QMediaPlayer::PausedState:
|
|
{
|
|
playAction->setEnabled(true);
|
|
pauseAction->setEnabled(false);
|
|
stopAction->setEnabled(true);
|
|
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
void Core::trayClicked(QSystemTrayIcon::ActivationReason reason)
|
|
{
|
|
if (reason == QSystemTrayIcon::DoubleClick)
|
|
{
|
|
nextAction->trigger();
|
|
}
|
|
else if (reason == QSystemTrayIcon::Trigger)
|
|
{
|
|
if (mediaState == QMediaPlayer::PlayingState)
|
|
{
|
|
pauseAction->trigger();
|
|
}
|
|
else if ((mediaState == QMediaPlayer::PausedState) || (mediaState == QMediaPlayer::StoppedState))
|
|
{
|
|
playAction->trigger();
|
|
}
|
|
}
|
|
}
|
|
|
|
void Core::volumeDown()
|
|
{
|
|
volumeSlider->setValue(volumeSlider->value() - 4);
|
|
}
|
|
|
|
void Core::volumeUp()
|
|
{
|
|
volumeSlider->setValue(volumeSlider->value() + 4);
|
|
}
|
|
|
|
void Core::showLicense()
|
|
{
|
|
QDialog dialog;
|
|
QFile file(":/gpl", this);
|
|
|
|
file.open(QFile::ReadOnly);
|
|
|
|
QPlainTextEdit *text = new QPlainTextEdit(file.readAll());
|
|
QVBoxLayout *layout = new QVBoxLayout(&dialog);
|
|
QRect rect = QApplication::desktop()->screenGeometry();
|
|
|
|
layout->addWidget(text);
|
|
text->setReadOnly(true);
|
|
text->setWordWrapMode(QTextOption::WrapAtWordBoundaryOrAnywhere);
|
|
|
|
file.close();
|
|
dialog.setMinimumWidth(rect.width() / 4);
|
|
dialog.setMinimumHeight(rect.height() - (rect.height() / 6));
|
|
dialog.exec();
|
|
}
|
|
|
|
QStringList Core::audExtensions()
|
|
{
|
|
QStringList ret;
|
|
|
|
// the media plugins are not checked for file format support. this is just an arbitrary list
|
|
// of audio file extentions that are used when filtering for files to play. if a file fails
|
|
// to play, the application will remain in silence or move on to the next file if nextCheck
|
|
// is enabled.
|
|
|
|
ret.append("*.mp3");
|
|
ret.append("*.ogg");
|
|
ret.append("*.flac");
|
|
ret.append("*.wav");
|
|
ret.append("*.au");
|
|
ret.append("*.aif");
|
|
ret.append("*.mid");
|
|
ret.append("*.rmi");
|
|
ret.append("*.m4a");
|
|
ret.append("*.aiff");
|
|
ret.append("*.ape");
|
|
ret.append("*.wv");
|
|
ret.append("*.3gp");
|
|
ret.append("*.aa");
|
|
ret.append("*.aac");
|
|
ret.append("*.aax");
|
|
ret.append("*.act");
|
|
ret.append("*.amr");
|
|
ret.append("*.au");
|
|
ret.append("*.awb");
|
|
ret.append("*.dct");
|
|
ret.append("*.gsm");
|
|
ret.append("*.m4b");
|
|
ret.append("*.oga");
|
|
ret.append("*.mogg");
|
|
ret.append("*.ra");
|
|
ret.append("*.rm");
|
|
ret.append("*.raw");
|
|
|
|
return ret;
|
|
}
|