diff --git a/CMakeLists.txt b/CMakeLists.txt index 3ec9fd7..dca0df8 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -19,6 +19,8 @@ add_executable(pineapple-music mainwindow.h seekableslider.cpp seekableslider.h + playlistmodel.h + playlistmodel.cpp mainwindow.ui resources.qrc ) diff --git a/main.cpp b/main.cpp index aff48df..547b926 100644 --- a/main.cpp +++ b/main.cpp @@ -1,11 +1,35 @@ #include "mainwindow.h" #include +#include +#include int main(int argc, char *argv[]) { QApplication a(argc, argv); + + // parse commandline arguments + QCommandLineParser parser; + parser.addPositionalArgument("File list", QCoreApplication::translate("main", "File list.")); + parser.addHelpOption(); + + parser.process(a); + + QStringList urlStrList = parser.positionalArguments(); + QList urlList; + for (const QString & str : urlStrList) { + QUrl url = QUrl::fromLocalFile(str); + if (url.isValid()) { + urlList.append(url); + } + } + MainWindow w; w.show(); + + if (!urlList.isEmpty()) { + w.commandlinePlayAudioFiles(urlList); + } + return a.exec(); } diff --git a/mainwindow.cpp b/mainwindow.cpp index 4dae1d1..0d271a7 100644 --- a/mainwindow.cpp +++ b/mainwindow.cpp @@ -1,6 +1,8 @@ #include "mainwindow.h" #include "./ui_mainwindow.h" +#include "playlistmodel.h" + #include #include #include @@ -9,11 +11,14 @@ #include #include #include +#include +#include MainWindow::MainWindow(QWidget *parent) : QMainWindow(parent) , ui(new Ui::MainWindow) , m_mediaPlayer(new QMediaPlayer(this)) + , m_playlistModel(new PlaylistModel(this)) { ui->setupUi(this); @@ -31,6 +36,45 @@ MainWindow::~MainWindow() delete ui; } +void MainWindow::commandlinePlayAudioFiles(QList audioFiles) +{ + if (!audioFiles.isEmpty()) { + if (audioFiles.count() == 1) { + loadPlaylistBySingleLocalFile(audioFiles.first().toLocalFile()); + } else { + createPlaylist(audioFiles); + } + m_mediaPlayer->play(); + } +} + +void MainWindow::loadPlaylistBySingleLocalFile(const QString &path) +{ + QFileInfo info(path); + QDir dir(info.path()); + QString currentFileName = info.fileName(); + QStringList entryList = dir.entryList({"*.mp3", "*.wav", "*.aiff", "*.ape", "*.flac", "*.ogg", "*.oga"}, + QDir::Files | QDir::NoSymLinks, QDir::NoSort); + + QCollator collator; + collator.setNumericMode(true); + + std::sort(entryList.begin(), entryList.end(), collator); + + QList urlList; + int currentFileIndex = -1; + for (int i = 0; i < entryList.count(); i++) { + const QString & oneEntry = entryList.at(i); + urlList.append(QUrl::fromLocalFile(dir.absoluteFilePath(oneEntry))); + if (oneEntry == currentFileName) { + currentFileIndex = i; + } + } + + QMediaPlaylist * playlist = createPlaylist(urlList); + playlist->setCurrentIndex(currentFileIndex); +} + void MainWindow::closeEvent(QCloseEvent *) { qApp->exit(); @@ -86,16 +130,33 @@ void MainWindow::loadFile() tr("Select songs to play"), QDir::homePath(), tr("Audio Files") + " (*.mp3 *.wav *.aiff *.ape *.flac *.ogg *.oga)"); + QList urlList; + for (const QString & fileName : files) { + urlList.append(QUrl::fromLocalFile(fileName)); + } + + createPlaylist(urlList); +} + +/* + * The returned QMediaPlaylist* ownership belongs to the internal QMediaPlayer instance. + */ +QMediaPlaylist *MainWindow::createPlaylist(QList urlList) +{ QMediaPlaylist * playlist = new QMediaPlaylist(m_mediaPlayer); playlist->setPlaybackMode(QMediaPlaylist::CurrentItemInLoop); - for (const QString & fileName : files) { - bool succ = playlist->addMedia(QMediaContent(QUrl::fromLocalFile(fileName))); + + for (const QUrl & url : urlList) { + bool succ = playlist->addMedia(QMediaContent(url)); if (!succ) { qDebug("!!!!!!!!! break point time !!!!!!!!!"); } } m_mediaPlayer->setPlaylist(playlist); + m_playlistModel->setPlaylist(playlist); + + return playlist; } void MainWindow::centerWindow() @@ -208,12 +269,22 @@ void MainWindow::initUiAndAnimation() m_fadeOutAnimation->setStartValue(1); m_fadeOutAnimation->setEndValue(0); connect(m_fadeOutAnimation, &QPropertyAnimation::finished, this, &QMainWindow::close); + + // temp: a playlist for debug... + QListView * tmp_listview = new QListView(ui->pluginWidget); + tmp_listview->setModel(m_playlistModel); + tmp_listview->setGeometry({0,0,490,250}); + this->setGeometry({0,0,490,160}); // temp size, hide the playlist thing. } void MainWindow::initConnections() { connect(m_mediaPlayer, &QMediaPlayer::currentMediaChanged, this, [=](const QMediaContent &media) { +#if QT_VERSION < QT_VERSION_CHECK(5, 14, 0) + ui->titleLabel->setText(media.canonicalUrl().fileName()); +#else ui->titleLabel->setText(media.request().url().fileName()); +#endif // QT_VERSION < QT_VERSION_CHECK(5, 0, 0) }); connect(m_mediaPlayer, &QMediaPlayer::positionChanged, this, [=](qint64 pos) { diff --git a/mainwindow.h b/mainwindow.h index 46f2506..8e7e43d 100644 --- a/mainwindow.h +++ b/mainwindow.h @@ -7,9 +7,11 @@ QT_BEGIN_NAMESPACE namespace Ui { class MainWindow; } class QMediaPlayer; +class QMediaPlaylist; class QPropertyAnimation; QT_END_NAMESPACE +class PlaylistModel; class MainWindow : public QMainWindow { Q_OBJECT @@ -18,6 +20,9 @@ public: MainWindow(QWidget *parent = nullptr); ~MainWindow() override; + void commandlinePlayAudioFiles(QList audioFiles); + void loadPlaylistBySingleLocalFile(const QString &path); + protected: void closeEvent(QCloseEvent *) override; void paintEvent(QPaintEvent *e) override; @@ -27,21 +32,17 @@ protected: void loadFile(); void centerWindow(); + QMediaPlaylist *createPlaylist(QList urlList); private slots: void on_closeWindowBtn_clicked(); void on_playBtn_clicked(); void on_volumeSlider_valueChanged(int value); void on_stopBtn_clicked(); - void on_playbackSlider_valueChanged(int value); - void on_prevBtn_clicked(); - void on_nextBtn_clicked(); - void on_volumeBtn_clicked(); - void on_minimumWindowBtn_clicked(); private: @@ -54,6 +55,7 @@ private: QMediaPlayer *m_mediaPlayer; QPropertyAnimation *m_fadeOutAnimation; + PlaylistModel *m_playlistModel = nullptr; // TODO: move playback logic to player.cpp void initUiAndAnimation(); void initConnections(); diff --git a/mainwindow.ui b/mainwindow.ui index 2de7e47..c9b0d1d 100644 --- a/mainwindow.ui +++ b/mainwindow.ui @@ -6,8 +6,8 @@ 0 0 - 485 - 160 + 490 + 420 @@ -106,12 +106,12 @@ QLabel#coverLabel { 0 - + 7 - 10 + 0 0 @@ -123,412 +123,460 @@ QLabel#coverLabel { 0 - - - 10 + + + + 0 + 0 + - - - - - 0 - 0 - - - - - 128 - 128 - - - - - 128 - 128 - - - - AlbumCover - - - - - - - 0 - - - - - 0 - - - - - Qt::Horizontal - - - - 40 - 20 - - - - - - - - - 25 - 20 - - - - ^ - - - - - - - - 25 - 20 - - - - - - - - :/icons/icons/window-minimize.png:/icons/icons/window-minimize.png - - - - - - - - 40 - 20 - - - - - 40 - 20 - - - - - - - - :/icons/icons/window-close.png:/icons/icons/window-close.png - - - - - - - - - QLayout::SetDefaultConstraint - - - 10 - - - 10 - - - - - - 0 - 0 - - - - No song loaded... - - - - - - - 44100 Hz | 233 Kbps | Stereo | MP3 - - - - - - - - - 0:00 - - - - - - - 0:00 - - - Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter - - - - - - - - - 1000 - - - Qt::Horizontal - - - - - - - 5 - - - - - - 32 - 32 - - - - - 32 - 32 - - - - - - - - - - - :/icons/icons/media-playback-start.png:/icons/icons/media-playback-start.png - - - - 32 - 32 - - - - - - - - - 32 - 32 - - - - - 32 - 32 - - - - - - - - :/icons/icons/media-playback-stop.png:/icons/icons/media-playback-stop.png - - - - 32 - 32 - - - - - - - - - 32 - 32 - - - - - 32 - 32 - - - - - - - - :/icons/icons/media-skip-backward.png:/icons/icons/media-skip-backward.png - - - - 32 - 32 - - - - - - - - - 32 - 32 - - - - - 32 - 32 - - - - - - - - :/icons/icons/media-skip-forward.png:/icons/icons/media-skip-forward.png - - - - 32 - 32 - - - - - - - - - 32 - 32 - - - - - 32 - 32 - - - - - - - - :/icons/icons/view-media-playlist.png:/icons/icons/view-media-playlist.png - - - - 32 - 32 - - - - - - - - - 32 - 32 - - - - - 32 - 32 - - - - - - - - :/icons/icons/media-playlist-repeat.png:/icons/icons/media-playlist-repeat.png - - - - 32 - 32 - - - - - - - - - 32 - 32 - - - - - 32 - 32 - - - - - - - - :/icons/icons/audio-volume-high.png:/icons/icons/audio-volume-high.png - - - - 32 - 32 - - - - - - - - - 64 - 32 - - - - 100 - - - 100 - - - Qt::Horizontal - - - - - - - - - - + + + 0 + 160 + + + + + 16777215 + 160 + + + + + 10 + + + 10 + + + 0 + + + 0 + + + 0 + + + + + + 0 + 0 + + + + + 128 + 128 + + + + + 128 + 128 + + + + AlbumCover + + + + + + + 0 + + + + + 0 + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + + 25 + 20 + + + + ^ + + + + + + + + 25 + 20 + + + + + + + + :/icons/icons/window-minimize.png:/icons/icons/window-minimize.png + + + + + + + + 40 + 20 + + + + + 40 + 20 + + + + + + + + :/icons/icons/window-close.png:/icons/icons/window-close.png + + + + + + + + + QLayout::SetDefaultConstraint + + + 10 + + + 10 + + + + + + 0 + 0 + + + + No song loaded... + + + + + + + 44100 Hz | 233 Kbps | Stereo | MP3 + + + + + + + + + 0:00 + + + + + + + 0:00 + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + + + + + + + 1000 + + + Qt::Horizontal + + + + + + + 5 + + + + + + 32 + 32 + + + + + 32 + 32 + + + + + + + + + + + :/icons/icons/media-playback-start.png:/icons/icons/media-playback-start.png + + + + 32 + 32 + + + + + + + + + 32 + 32 + + + + + 32 + 32 + + + + + + + + :/icons/icons/media-playback-stop.png:/icons/icons/media-playback-stop.png + + + + 32 + 32 + + + + + + + + + 32 + 32 + + + + + 32 + 32 + + + + + + + + :/icons/icons/media-skip-backward.png:/icons/icons/media-skip-backward.png + + + + 32 + 32 + + + + + + + + + 32 + 32 + + + + + 32 + 32 + + + + + + + + :/icons/icons/media-skip-forward.png:/icons/icons/media-skip-forward.png + + + + 32 + 32 + + + + + + + + + 32 + 32 + + + + + 32 + 32 + + + + + + + + :/icons/icons/view-media-playlist.png:/icons/icons/view-media-playlist.png + + + + 32 + 32 + + + + + + + + + 32 + 32 + + + + + 32 + 32 + + + + + + + + :/icons/icons/media-playlist-repeat.png:/icons/icons/media-playlist-repeat.png + + + + 32 + 32 + + + + + + + + + 32 + 32 + + + + + 32 + 32 + + + + + + + + :/icons/icons/audio-volume-high.png:/icons/icons/audio-volume-high.png + + + + 32 + 32 + + + + + + + + + 64 + 32 + + + + 100 + + + 100 + + + Qt::Horizontal + + + + + + + + + + + + + + + + + 0 + 0 + + + + + 0 + 0 + + + diff --git a/playlistmodel.cpp b/playlistmodel.cpp new file mode 100644 index 0000000..a7b7c27 --- /dev/null +++ b/playlistmodel.cpp @@ -0,0 +1,174 @@ +/**************************************************************************** +** +** Copyright (C) 2017 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the examples of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:BSD$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** BSD License Usage +** Alternatively, you may use this file under the terms of the BSD license +** as follows: +** +** "Redistribution and use in source and binary forms, with or without +** modification, are permitted provided that the following conditions are +** met: +** * Redistributions of source code must retain the above copyright +** notice, this list of conditions and the following disclaimer. +** * Redistributions in binary form must reproduce the above copyright +** notice, this list of conditions and the following disclaimer in +** the documentation and/or other materials provided with the +** distribution. +** * Neither the name of The Qt Company Ltd nor the names of its +** contributors may be used to endorse or promote products derived +** from this software without specific prior written permission. +** +** +** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +** "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +** LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +** A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +** OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +** LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +** OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE." +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + + +#include "playlistmodel.h" + +#include +#include +#include + +PlaylistModel::PlaylistModel(QObject *parent) + : QAbstractItemModel(parent) +{ +} + +PlaylistModel::~PlaylistModel() +{ +} + +int PlaylistModel::rowCount(const QModelIndex &parent) const +{ + return m_playlist && !parent.isValid() ? m_playlist->mediaCount() : 0; +} + +int PlaylistModel::columnCount(const QModelIndex &parent) const +{ + return !parent.isValid() ? ColumnCount : 0; +} + +QModelIndex PlaylistModel::index(int row, int column, const QModelIndex &parent) const +{ + return m_playlist && !parent.isValid() + && row >= 0 && row < m_playlist->mediaCount() + && column >= 0 && column < ColumnCount + ? createIndex(row, column) + : QModelIndex(); +} + +QModelIndex PlaylistModel::parent(const QModelIndex &child) const +{ + Q_UNUSED(child); + + return QModelIndex(); +} + +QVariant PlaylistModel::data(const QModelIndex &index, int role) const +{ + if (index.isValid() && role == Qt::DisplayRole) { + QVariant value = m_data[index]; + if (!value.isValid() && index.column() == Title) { +#if QT_VERSION < QT_VERSION_CHECK(5, 14, 0) + QUrl location = m_playlist->media(index.row()).canonicalUrl(); +#else + QUrl location = m_playlist->media(index.row()).request().url(); +#endif + return QFileInfo(location.path()).fileName(); + } + + return value; + } + return QVariant(); +} + +QMediaPlaylist *PlaylistModel::playlist() const +{ + return m_playlist.data(); +} + +void PlaylistModel::setPlaylist(QMediaPlaylist *playlist) +{ + if (m_playlist) { + disconnect(m_playlist.data(), &QMediaPlaylist::mediaAboutToBeInserted, this, &PlaylistModel::beginInsertItems); + disconnect(m_playlist.data(), &QMediaPlaylist::mediaInserted, this, &PlaylistModel::endInsertItems); + disconnect(m_playlist.data(), &QMediaPlaylist::mediaAboutToBeRemoved, this, &PlaylistModel::beginRemoveItems); + disconnect(m_playlist.data(), &QMediaPlaylist::mediaRemoved, this, &PlaylistModel::endRemoveItems); + disconnect(m_playlist.data(), &QMediaPlaylist::mediaChanged, this, &PlaylistModel::changeItems); + } + + beginResetModel(); + m_playlist.reset(playlist); + + if (m_playlist) { + connect(m_playlist.data(), &QMediaPlaylist::mediaAboutToBeInserted, this, &PlaylistModel::beginInsertItems); + connect(m_playlist.data(), &QMediaPlaylist::mediaInserted, this, &PlaylistModel::endInsertItems); + connect(m_playlist.data(), &QMediaPlaylist::mediaAboutToBeRemoved, this, &PlaylistModel::beginRemoveItems); + connect(m_playlist.data(), &QMediaPlaylist::mediaRemoved, this, &PlaylistModel::endRemoveItems); + connect(m_playlist.data(), &QMediaPlaylist::mediaChanged, this, &PlaylistModel::changeItems); + } + + endResetModel(); +} + +bool PlaylistModel::setData(const QModelIndex &index, const QVariant &value, int role) +{ + Q_UNUSED(role); + m_data[index] = value; + emit dataChanged(index, index); + return true; +} + +void PlaylistModel::beginInsertItems(int start, int end) +{ + m_data.clear(); + beginInsertRows(QModelIndex(), start, end); +} + +void PlaylistModel::endInsertItems() +{ + endInsertRows(); +} + +void PlaylistModel::beginRemoveItems(int start, int end) +{ + m_data.clear(); + beginRemoveRows(QModelIndex(), start, end); +} + +void PlaylistModel::endRemoveItems() +{ + endInsertRows(); +} + +void PlaylistModel::changeItems(int start, int end) +{ + m_data.clear(); + emit dataChanged(index(start,0), index(end,ColumnCount)); +} diff --git a/playlistmodel.h b/playlistmodel.h new file mode 100644 index 0000000..1ce4df3 --- /dev/null +++ b/playlistmodel.h @@ -0,0 +1,99 @@ +/**************************************************************************** +** +** Copyright (C) 2017 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the examples of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:BSD$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** BSD License Usage +** Alternatively, you may use this file under the terms of the BSD license +** as follows: +** +** "Redistribution and use in source and binary forms, with or without +** modification, are permitted provided that the following conditions are +** met: +** * Redistributions of source code must retain the above copyright +** notice, this list of conditions and the following disclaimer. +** * Redistributions in binary form must reproduce the above copyright +** notice, this list of conditions and the following disclaimer in +** the documentation and/or other materials provided with the +** distribution. +** * Neither the name of The Qt Company Ltd nor the names of its +** contributors may be used to endorse or promote products derived +** from this software without specific prior written permission. +** +** +** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +** "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +** LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +** A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +** OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +** LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +** OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE." +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + + +#ifndef PLAYLISTMODEL_H +#define PLAYLISTMODEL_H + +#include +#include + +class QMediaPlaylist; + +class PlaylistModel : public QAbstractItemModel +{ + Q_OBJECT + +public: + enum Column + { + Title = 0, + ColumnCount + }; + + explicit PlaylistModel(QObject *parent = nullptr); + ~PlaylistModel(); + + int rowCount(const QModelIndex &parent = QModelIndex()) const override; + int columnCount(const QModelIndex &parent = QModelIndex()) const override; + + QModelIndex index(int row, int column, const QModelIndex &parent = QModelIndex()) const override; + QModelIndex parent(const QModelIndex &child) const override; + + QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override; + + QMediaPlaylist *playlist() const; + void setPlaylist(QMediaPlaylist *playlist); + + bool setData(const QModelIndex &index, const QVariant &value, int role = Qt::DisplayRole) override; + +private slots: + void beginInsertItems(int start, int end); + void endInsertItems(); + void beginRemoveItems(int start, int end); + void endRemoveItems(); + void changeItems(int start, int end); + +private: + QScopedPointer m_playlist; + QMap m_data; +}; + +#endif // PLAYLISTMODEL_H