I've fully written out code for the base of the app and tested to to be

working, also added svg icons to the user interface. the settings button
currently does nothing (still work in progress). during testing, i
discovered QMediaPlayer would have trouble playing some music files that
have an ID3 tag so AudFile was created as a way to read the size of the
ID3 tag and step over it when QMediaPlayer reads from the file. although
it fixed the playback issue, QMediaPlayer is now having trouble
calculating the duration properly without the tag. i already have a
solution to the porblem in mind, more to come in the next commit.
This commit is contained in:
Maurice O'Neal 2016-10-10 13:29:41 -04:00 committed by Maurice O'Neal
parent 455723cede
commit 10e5af0588
14 changed files with 582 additions and 77 deletions

View File

@ -5,17 +5,21 @@
#------------------------------------------------- #-------------------------------------------------
QT += core gui QT += core gui
QT += multimedia
QT += svg
greaterThan(QT_MAJOR_VERSION, 4): QT += widgets greaterThan(QT_MAJOR_VERSION, 4): QT += widgets
TARGET = JustAudio TARGET = JustAudio
TEMPLATE = app TEMPLATE = app
SOURCES += main.cpp\ SOURCES += main.cpp\
gui/ui.cpp \ gui/ui.cpp\
gui/file_item.cpp gui/icon.cpp \
io/aud_file.cpp
HEADERS += \ HEADERS += gui/ui.h\
gui/ui.h \ gui/icon.h \
gui/file_item.h io/aud_file.h
RESOURCES += icon_files.qrc

View File

@ -1,6 +0,0 @@
#include "file_item.h"
FileItem::FileItem(QWidget *parent) : QLabel(parent)
{
}

View File

@ -1,17 +0,0 @@
#ifndef FILE_ITEM_H
#define FILE_ITEM_H
#include <QWidget>
#include <QLabel>
#include <QHBoxLayout>
class FileItem : public QLabel
{
Q_OBJECT
public:
FileItem(QWidget *parent = 0);
};
#endif // FILE_ITEM_H

83
gui/icon.cpp Normal file
View File

@ -0,0 +1,83 @@
#include "icon.h"
Icon::Icon(IconType t, QWidget *parent) : QLabel(parent)
{
QRect rect = QApplication::desktop()->screenGeometry();
setMinimumHeight(rect.height() / 40);
setMinimumWidth(rect.height() / 40);
setCursor(Qt::PointingHandCursor);
switch (t)
{
case PAUSE_PLAY: stateChanged(QMediaPlayer::StoppedState); break;
case SETTINGS: loadImg(":/settings"); break;
case OPEN: loadImg(":/open"); break;
}
type = t;
}
void Icon::mouseReleaseEvent(QMouseEvent *event)
{
if (event->button() == Qt::LeftButton)
{
switch (type)
{
case PAUSE_PLAY:
{
if (playerState == QMediaPlayer::PlayingState)
{
emit pause();
}
else
{
emit play();
}
break;
}
case SETTINGS:
{
emit settings();
break;
}
case OPEN:
{
emit open();
break;
}
}
}
}
void Icon::paintEvent(QPaintEvent *)
{
QPainter painter(this);
QSvgRenderer svg(svgFile, this);
svg.render(&painter);
}
void Icon::stateChanged(QMediaPlayer::State state)
{
playerState = state;
if (state == QMediaPlayer::PlayingState)
{
loadImg(":/pause");
}
else
{
loadImg(":/play");
}
}
void Icon::loadImg(const QString &path)
{
svgFile = path;
update();
}

53
gui/icon.h Normal file
View File

@ -0,0 +1,53 @@
#ifndef ICON_H
#define ICON_H
#include <QWidget>
#include <QLabel>
#include <QMediaPlayer>
#include <QMouseEvent>
#include <QSvgRenderer>
#include <QPixmap>
#include <QPainter>
#include <QPaintEvent>
#include <QApplication>
#include <QRect>
#include <QDesktopWidget>
class Icon : public QLabel
{
Q_OBJECT
public:
enum IconType
{
PAUSE_PLAY,
SETTINGS,
OPEN
};
Icon(IconType t, QWidget *parent = 0);
public slots:
void stateChanged(QMediaPlayer::State state);
private:
IconType type;
QMediaPlayer::State playerState;
QString svgFile;
void mouseReleaseEvent(QMouseEvent *event);
void paintEvent(QPaintEvent *);
void loadImg(const QString &path);
signals:
void pause();
void play();
void settings();
void open();
};
#endif // ICON_H

View File

@ -2,53 +2,160 @@
Ui::Ui(const QStringList &args, QWidget *parent) : QWidget(parent) Ui::Ui(const QStringList &args, QWidget *parent) : QWidget(parent)
{ {
QWidget *listWid = new QWidget(this); QWidget *btnWid = new QWidget(this);
QScrollArea *mid = new QScrollArea(this); QHBoxLayout *btnLayout = new QHBoxLayout(btnWid);
QVBoxLayout *mainLayout = new QVBoxLayout(this); QVBoxLayout *mainLayout = new QVBoxLayout(this);
fileName = new QLabel(this);
slider = new QSlider(this); slider = new QSlider(this);
listLayout = new QVBoxLayout(listWid); pausePlay = new Icon(Icon::PAUSE_PLAY, this);
search = new QLineEdit(this); settings = new Icon(Icon::SETTINGS, this);
open = new Icon(Icon::OPEN, this);
player = new QMediaPlayer(this);
ioDev = new AudFile(this);
pressed = false;
QFont fnt = fileName->font();
QRect rect = QApplication::desktop()->screenGeometry();
fnt.setBold(true);
setMinimumHeight(rect.height() / 6);
setMinimumWidth(rect.width() / 5);
setMaximumHeight(rect.height() / 6);
setMaximumWidth(rect.width() / 5);
setStyleSheet("background-color:white;");
fileName->setText(QApplication::applicationName());
fileName->setFont(fnt);
slider->setOrientation(Qt::Horizontal); slider->setOrientation(Qt::Horizontal);
mid->setWidget(listWid); slider->setMinimumWidth((rect.width() / 5) - 100);
mid->setWidgetResizable(true); btnLayout->addWidget(pausePlay, 0, Qt::AlignCenter);
mainLayout->addWidget(search); btnLayout->addSpacing(rect.height() / 40);
mainLayout->addWidget(mid); btnLayout->addWidget(open, 0, Qt::AlignCenter);
mainLayout->addWidget(slider); btnLayout->addSpacing(rect.height() / 40);
mainLayout->setSpacing(0); btnLayout->addWidget(settings, 0, Qt::AlignCenter);
mainLayout->setContentsMargins(0, 0, 0, 0); mainLayout->addWidget(fileName, 0, Qt::AlignCenter);
mainLayout->addWidget(slider, 0, Qt::AlignCenter);
mainLayout->addWidget(btnWid, 0, Qt::AlignCenter);
connect(pausePlay, SIGNAL(pause()), player, SLOT(pause()));
connect(pausePlay, SIGNAL(play()), player, SLOT(play()));
connect(open, SIGNAL(open()), this, SLOT(openDialog()));
connect(player, SIGNAL(error(QMediaPlayer::Error)), this, SLOT(error(QMediaPlayer::Error)));
connect(player, SIGNAL(stateChanged(QMediaPlayer::State)), pausePlay, SLOT(stateChanged(QMediaPlayer::State)));
connect(player, SIGNAL(durationChanged(qint64)), this, SLOT(durationChanged(qint64)));
connect(player, SIGNAL(positionChanged(qint64)), this, SLOT(posChanged(qint64)));
connect(slider, SIGNAL(sliderPressed()), this, SLOT(sliderPressed()));
connect(slider, SIGNAL(sliderReleased()), this, SLOT(sliderReleased()));
if (args.size() > 1) if (args.size() > 1)
{ {
QFileInfo info(args[1]); play(args[1]);
info.makeAbsolute();
if (info.isFile())
{
setActiveDir(info.path());
setActiveFile(info.filePath());
}
else if (info.isDir())
{
setActiveDir(info.path());
}
else
{
setActiveDir(QDir::homePath());
}
}
else
{
setActiveDir(QDir::homePath());
} }
} }
void Ui::setActiveDir(const QString &path) void Ui::play(const QString &path)
{ {
for (int i = 0; i < fileList.size(); ++i) QFileInfo info(path);
fileDir = info.path();
slider->setMinimum(0);
fileName->setText(info.fileName());
player->stop();
ioDev->close();
ioDev->openFile(path);
player->setMedia(0, ioDev);
player->play();
}
void Ui::openDialog()
{
QFileDialog fileDialog(this);
fileDialog.setFileMode(QFileDialog::ExistingFile);
fileDialog.setNameFilter(tr("Audio Files (*.mp3 *.ogg *.wav *.flac)"));
if (fileDialog.exec())
{ {
listLayout->removeWidget(fileList[i]); play(fileDialog.selectedFiles()[0]);
}
}
void Ui::error(QMediaPlayer::Error error)
{
QMessageBox box(this);
if (error == QMediaPlayer::NoError)
{
box.setText(tr("An unknown error has occured"));
}
else
{
box.setText(player->errorString());
}
if (error == QMediaPlayer::FormatError)
{
box.setIcon(QMessageBox::Warning);
}
else
{
box.setIcon(QMessageBox::Critical);
}
box.exec();
}
void Ui::sliderPressed()
{
pressed = true;
}
void Ui::sliderReleased()
{
pressed = false;
player->setPosition(slider->value());
}
void Ui::posChanged(qint64 pos)
{
if (!pressed) slider->setSliderPosition(pos);
// if (slider->sliderPosition() == slider->maximum())
// {
// QTimer::singleShot(750, this, SLOT(nextFile()));
// }
}
void Ui::durationChanged(qint64 len)
{
slider->setMaximum(len);
}
void Ui::nextFile()
{
QDir dir(fileDir);
QStringList filterList;
filterList.append("*.mp3");
filterList.append("*.ogg");
filterList.append("*.flac");
filterList.append("*.wav");
QStringList list = dir.entryList(filterList, QDir::Files | QDir::Readable, QDir::Name);
int pos = list.indexOf(fileName->text());
if (pos != -1)
{
pos++;
if (pos < list.size())
{
play(fileDir + list[pos]);
}
} }
} }

View File

@ -4,17 +4,23 @@
#include <QWidget> #include <QWidget>
#include <QVBoxLayout> #include <QVBoxLayout>
#include <QHBoxLayout> #include <QHBoxLayout>
#include <QScrollArea>
#include <QSlider> #include <QSlider>
#include <QLabel> #include <QLabel>
#include <QDir> #include <QDir>
#include <QFile> #include <QFile>
#include <QFileInfo> #include <QMediaPlayer>
#include <QLineEdit> #include <QUrl>
#include <QFileInfoList> #include <QFileDialog>
#include <QList> #include <QStringList>
#include <QMessageBox>
#include <QFont>
#include <QApplication>
#include <QRect>
#include <QDesktopWidget>
#include <QTimer>
#include "file_item.h" #include "icon.h"
#include "../io/aud_file.h"
class Ui : public QWidget class Ui : public QWidget
{ {
@ -22,19 +28,30 @@ class Ui : public QWidget
private: private:
QLabel *fileName;
QSlider *slider; QSlider *slider;
QLineEdit *search; QMediaPlayer *player;
QVBoxLayout *listLayout; AudFile *ioDev;
QList<FileItem*> fileList; Icon *pausePlay;
Icon *settings;
Icon *open;
QString fileDir;
bool pressed;
private slots:
void error(QMediaPlayer::Error error);
void sliderPressed();
void sliderReleased();
void posChanged(qint64 pos);
void durationChanged(qint64 len);
void openDialog();
void nextFile();
void play(const QString &path);
public: public:
Ui(const QStringList &args, QWidget *parent = 0); Ui(const QStringList &args, QWidget *parent = 0);
public slots:
void setActiveFile(const QString &name);
void setActiveDir(const QString &path);
}; };
#endif // UI_H #endif // UI_H

8
icon_files.qrc Normal file
View File

@ -0,0 +1,8 @@
<RCC>
<qresource prefix="/">
<file alias="play">svg/play-button.svg</file>
<file alias="pause">svg/pause.svg</file>
<file alias="settings">svg/list.svg</file>
<file alias="open">svg/eject.svg</file>
</qresource>
</RCC>

57
io/aud_file.cpp Normal file
View File

@ -0,0 +1,57 @@
#include "aud_file.h"
AudFile::AudFile(QObject *parent) : QFile(parent)
{
offset = 0;
}
AudFile::~AudFile()
{
close();
}
bool AudFile::openFile(const QString &path)
{
setFileName(path);
bool ret = open(QFile::ReadOnly);
if (ret)
{
if (peek(3) == "ID3")
{
QByteArray header = read(10);
QByteArray intBytes = header.mid(6);
quint64 num = 0;
quint64 bit = 1;
offset += 10;
if (header[5] & 16) //Footer flag check
{
offset += 10;
}
for (int i = intBytes.size() - 1; i >= 0; --i)
{
int byte = intBytes[i];
for (int inBit = 1; inBit <= 64; inBit *= 2, bit *= 2)
{
if ((byte & inBit) != 0) num |= bit;
}
}
offset += num;
seek(0);
}
}
return ret;
}
bool AudFile::seek(qint64 pos)
{
return QFile::seek(pos + offset);
}

26
io/aud_file.h Normal file
View File

@ -0,0 +1,26 @@
#ifndef AUD_FILE_H
#define AUD_FILE_H
#include <QFile>
#include <QObject>
#include <QString>
class AudFile : public QFile
{
Q_OBJECT
private:
qint64 offset;
public:
AudFile(QObject *parent = 0);
bool openFile(const QString &path);
bool seek(qint64 pos);
~AudFile();
};
#endif // AUD_FILE_H

46
svg/eject.svg Normal file
View File

@ -0,0 +1,46 @@
<?xml version="1.0" encoding="iso-8859-1"?>
<!-- Generator: Adobe Illustrator 19.0.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
<svg version="1.1" id="Capa_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
viewBox="0 0 56.654 56.654" style="enable-background:new 0 0 56.654 56.654;" xml:space="preserve">
<g>
<path d="M55.043,27.283L31.977,4.306c-0.946-0.942-2.27-1.479-3.66-1.479c-1.389,0-2.714,0.535-3.66,1.478L1.348,27.525
C1.29,27.582,1.235,27.64,1.18,27.699c-0.362,0.398-0.641,0.838-0.836,1.297c-0.193,0.459-0.311,0.949-0.338,1.463
C0.002,30.537,0,30.613,0,30.69c0,0.021,0,0.037,0,0.06c0.009,0.635,0.155,1.242,0.414,1.791c0.259,0.553,0.638,1.063,1.135,1.504
c0.002,0.002,0.004,0.004,0.004,0.004c0.07,0.063,0.144,0.123,0.218,0.182c0.002,0,0.002,0.002,0.004,0.002
c0.438,0.344,0.924,0.605,1.434,0.787c0.481,0.172,0.999,0.275,1.538,0.303c0.002,0,0.003,0,0.005,0
c0.086,0.002,0.142,0.002,0.262,0.004H51.62c0.006,0,0.01,0,0.015,0c0.007,0,0.015,0,0.019,0c2.762,0,5-2.073,5-4.635
C56.654,29.344,56.033,28.131,55.043,27.283z"/>
<path d="M56.654,48.161c0,3.129-2.537,5.666-5.668,5.666H5.667C2.537,53.827,0,51.29,0,48.161l0,0c0-3.131,2.537-5.668,5.667-5.668
h45.318C54.117,42.493,56.654,45.03,56.654,48.161L56.654,48.161z"/>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 1.4 KiB

46
svg/list.svg Normal file
View File

@ -0,0 +1,46 @@
<?xml version="1.0" encoding="iso-8859-1"?>
<!-- Generator: Adobe Illustrator 19.0.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
<svg version="1.1" id="Capa_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
viewBox="0 0 62.246 62.246" style="enable-background:new 0 0 62.246 62.246;" xml:space="preserve">
<g>
<path d="M57.548,45.107H19.965c-2.595,0-4.699,2.105-4.699,4.701c0,2.594,2.104,4.699,4.699,4.699h37.583
c2.594,0,4.698-2.105,4.698-4.699C62.246,47.213,60.142,45.107,57.548,45.107z"/>
<path d="M57.548,26.402H19.965c-2.595,0-4.699,2.104-4.699,4.7c0,2.595,2.104,4.699,4.699,4.699h37.583
c2.594,0,4.698-2.104,4.698-4.699S60.142,26.402,57.548,26.402z"/>
<path d="M19.965,17.096h37.583c2.594,0,4.698-2.104,4.698-4.7s-2.104-4.699-4.698-4.699H19.965c-2.595,0-4.699,2.104-4.699,4.699
C15.266,14.991,17.37,17.096,19.965,17.096z"/>
<circle cx="4.77" cy="12.439" r="4.77"/>
<circle cx="4.77" cy="31.102" r="4.769"/>
<circle cx="4.77" cy="49.807" r="4.77"/>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 1.1 KiB

41
svg/pause.svg Normal file
View File

@ -0,0 +1,41 @@
<?xml version="1.0" encoding="iso-8859-1"?>
<!-- Generator: Adobe Illustrator 19.0.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
<svg version="1.1" id="Capa_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
viewBox="0 0 47.607 47.607" style="enable-background:new 0 0 47.607 47.607;" xml:space="preserve">
<g>
<path d="M17.991,40.976c0,3.662-2.969,6.631-6.631,6.631l0,0c-3.662,0-6.631-2.969-6.631-6.631V6.631C4.729,2.969,7.698,0,11.36,0
l0,0c3.662,0,6.631,2.969,6.631,6.631V40.976z"/>
<path d="M42.877,40.976c0,3.662-2.969,6.631-6.631,6.631l0,0c-3.662,0-6.631-2.969-6.631-6.631V6.631
C29.616,2.969,32.585,0,36.246,0l0,0c3.662,0,6.631,2.969,6.631,6.631V40.976z"/>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 875 B

40
svg/play-button.svg Normal file
View File

@ -0,0 +1,40 @@
<?xml version="1.0" encoding="iso-8859-1"?>
<!-- Generator: Adobe Illustrator 19.0.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
<svg version="1.1" id="Capa_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
viewBox="0 0 47.604 47.604" style="enable-background:new 0 0 47.604 47.604;" xml:space="preserve">
<g>
<path d="M43.331,21.237L7.233,0.397c-0.917-0.529-2.044-0.529-2.96,0c-0.916,0.528-1.48,1.505-1.48,2.563v41.684
c0,1.058,0.564,2.035,1.48,2.563c0.458,0.268,0.969,0.397,1.48,0.397c0.511,0,1.022-0.133,1.48-0.397l36.098-20.84
c0.918-0.529,1.479-1.506,1.479-2.564S44.247,21.767,43.331,21.237z"/>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 811 B