diff --git a/src/mediaplaylist.cpp b/src/mediaplaylist.cpp index a48678aa..4936c080 100644 --- a/src/mediaplaylist.cpp +++ b/src/mediaplaylist.cpp @@ -1,1346 +1,1390 @@ /* * Copyright 2015-2017 Matthieu Gallien * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public * License as published by the Free Software Foundation; either * version 3 of the License, or (at your option) any later version. * * This library 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 * Library General Public License for more details. * * You should have received a copy of the GNU Library General Public License * along with this library; see the file COPYING.LIB. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #include "mediaplaylist.h" #include "databaseinterface.h" #include "musicaudiotrack.h" #include "musiclistenersmanager.h" #include #include #include #include #include #include #include class MediaPlayListPrivate { public: QList mData; QList mTrackData; MusicListenersManager* mMusicListenersManager = nullptr; QPersistentModelIndex mCurrentTrack; QVariantMap mPersistentState; QMediaPlaylist mLoadPlaylist; int mCurrentPlayListPosition = 0; bool mRandomPlay = false; bool mRepeatPlay = false; }; MediaPlayList::MediaPlayList(QObject *parent) : QAbstractListModel(parent), d(new MediaPlayListPrivate) { connect(&d->mLoadPlaylist, &QMediaPlaylist::loaded, this, &MediaPlayList::loadPlayListLoaded); connect(&d->mLoadPlaylist, &QMediaPlaylist::loadFailed, this, &MediaPlayList::loadPlayListLoadFailed); seedRandomGenerator(QTime::currentTime().msec()); } MediaPlayList::~MediaPlayList() = default; int MediaPlayList::rowCount(const QModelIndex &parent) const { if (parent.isValid()) { return 0; } return d->mData.size(); } QHash MediaPlayList::roleNames() const { auto roles = QAbstractItemModel::roleNames(); roles[static_cast(ColumnsRoles::IsValidRole)] = "isValid"; roles[static_cast(ColumnsRoles::TitleRole)] = "title"; roles[static_cast(ColumnsRoles::DurationRole)] = "duration"; roles[static_cast(ColumnsRoles::ArtistRole)] = "artist"; roles[static_cast(ColumnsRoles::AlbumArtistRole)] = "albumArtist"; roles[static_cast(ColumnsRoles::AlbumRole)] = "album"; roles[static_cast(ColumnsRoles::TrackNumberRole)] = "trackNumber"; roles[static_cast(ColumnsRoles::DiscNumberRole)] = "discNumber"; roles[static_cast(ColumnsRoles::RatingRole)] = "rating"; roles[static_cast(ColumnsRoles::GenreRole)] = "genre"; roles[static_cast(ColumnsRoles::LyricistRole)] = "lyricist"; roles[static_cast(ColumnsRoles::ComposerRole)] = "composer"; roles[static_cast(ColumnsRoles::CommentRole)] = "comment"; roles[static_cast(ColumnsRoles::YearRole)] = "year"; roles[static_cast(ColumnsRoles::ChannelsRole)] = "channels"; roles[static_cast(ColumnsRoles::BitRateRole)] = "bitRate"; roles[static_cast(ColumnsRoles::SampleRateRole)] = "sampleRate"; roles[static_cast(ColumnsRoles::ImageRole)] = "image"; roles[static_cast(ColumnsRoles::CountRole)] = "count"; roles[static_cast(ColumnsRoles::IsPlayingRole)] = "isPlaying"; roles[static_cast(ColumnsRoles::HasAlbumHeader)] = "hasAlbumHeader"; roles[static_cast(ColumnsRoles::IsSingleDiscAlbumHeader)] = "isSingleDiscAlbum"; roles[static_cast(ColumnsRoles::SecondaryTextRole)] = "secondaryText"; roles[static_cast(ColumnsRoles::ImageUrlRole)] = "imageUrl"; roles[static_cast(ColumnsRoles::ShadowForImageRole)] = "shadowForImage"; roles[static_cast(ColumnsRoles::ResourceRole)] = "trackResource"; roles[static_cast(ColumnsRoles::TrackDataRole)] = "trackData"; return roles; } QVariant MediaPlayList::data(const QModelIndex &index, int role) const { auto result = QVariant(); if (!index.isValid()) { return result; } if (index.row() < 0 || index.row() >= d->mData.size()) { return result; } if (d->mData[index.row()].mIsValid) { switch(role) { case ColumnsRoles::IsValidRole: result = d->mData[index.row()].mIsValid; break; case ColumnsRoles::TitleRole: if (!d->mTrackData[index.row()].title().isEmpty()) { result = d->mTrackData[index.row()].title(); } else { if (d->mData[index.row()].mTrackUrl.isLocalFile()) { auto localFile = QFileInfo(d->mData[index.row()].mTrackUrl.toLocalFile()); result = localFile.fileName(); } else { result = d->mData[index.row()].mTrackUrl.toString(); } } break; case ColumnsRoles::DurationRole: { const QTime &trackDuration = d->mTrackData[index.row()].duration(); if (trackDuration.hour() == 0) { result = trackDuration.toString(QStringLiteral("mm:ss")); } else { result = trackDuration.toString(); } break; } case ColumnsRoles::MilliSecondsDurationRole: result = d->mTrackData[index.row()].duration().msecsSinceStartOfDay(); break; case ColumnsRoles::ArtistRole: result = d->mTrackData[index.row()].artist(); break; case ColumnsRoles::AlbumArtistRole: result = d->mTrackData[index.row()].albumArtist(); break; case ColumnsRoles::AlbumRole: result = d->mTrackData[index.row()].albumName(); break; case ColumnsRoles::TrackNumberRole: result = d->mTrackData[index.row()].trackNumber(); break; case ColumnsRoles::DiscNumberRole: result = d->mTrackData[index.row()].discNumber(); break; case ColumnsRoles::IsSingleDiscAlbumHeader: result = d->mTrackData[index.row()].isSingleDiscAlbum(); break; case ColumnsRoles::ResourceRole: if (d->mTrackData[index.row()].resourceURI().isValid()) { result = d->mTrackData[index.row()].resourceURI(); } else { result = d->mData[index.row()].mTrackUrl; } break; case ColumnsRoles::ImageRole: { auto albumArt = d->mTrackData[index.row()].albumCover(); if (albumArt.isValid()) { result = albumArt; } break; } case ColumnsRoles::HasAlbumHeader: result = rowHasHeader(index.row()); break; case ColumnsRoles::RatingRole: result = d->mTrackData[index.row()].rating(); break; case ColumnsRoles::GenreRole: result = d->mTrackData[index.row()].genre(); break; case ColumnsRoles::LyricistRole: result = d->mTrackData[index.row()].lyricist(); break; case ColumnsRoles::ComposerRole: result = d->mTrackData[index.row()].composer(); break; case ColumnsRoles::CommentRole: result = d->mTrackData[index.row()].comment(); break; case ColumnsRoles::YearRole: result = d->mTrackData[index.row()].year(); break; case ColumnsRoles::ChannelsRole: result = d->mTrackData[index.row()].channels(); break; case ColumnsRoles::BitRateRole: result = d->mTrackData[index.row()].bitRate(); break; case ColumnsRoles::SampleRateRole: result = d->mTrackData[index.row()].sampleRate(); break; case ColumnsRoles::CountRole: break; case ColumnsRoles::IsPlayingRole: result = d->mData[index.row()].mIsPlaying; break; case Qt::DisplayRole: { const auto &track = d->mTrackData[index.row()]; auto displayText = QString(); displayText = QStringLiteral("%1 - %2"); if (track.isSingleDiscAlbum()) { displayText = displayText.arg(track.trackNumber()); } else { auto numbersText = QString(); numbersText = QStringLiteral("%1 - %2"); numbersText = numbersText.arg(track.discNumber()); numbersText = numbersText.arg(track.trackNumber()); displayText = displayText.arg(numbersText); } result = displayText.arg(track.title()); break; } case ColumnsRoles::SecondaryTextRole: break; case ColumnsRoles::ImageUrlRole: { const auto &albumArt = d->mTrackData[index.row()].albumCover(); if (albumArt.isValid()) { result = albumArt; } else { result = QUrl(QStringLiteral("image://icon/media-optical-audio")); } break; } case ColumnsRoles::ShadowForImageRole: result = d->mTrackData[index.row()].albumCover().isValid(); break; case ColumnsRoles::TrackDataRole: result = QVariant::fromValue(d->mTrackData[index.row()]); break; } } else { switch(role) { case ColumnsRoles::IsValidRole: result = d->mData[index.row()].mIsValid; break; case ColumnsRoles::TitleRole: if (!d->mData[index.row()].mTitle.isEmpty()) { result = d->mData[index.row()].mTitle; } else if (d->mData[index.row()].mTrackUrl.isValid()) { if (d->mData[index.row()].mTrackUrl.isLocalFile()) { auto localFile = QFileInfo(d->mData[index.row()].mTrackUrl.toLocalFile()); result = localFile.fileName(); } else { result = d->mData[index.row()].mTrackUrl.toString(); } } break; case ColumnsRoles::IsPlayingRole: result = d->mData[index.row()].mIsPlaying; break; case ColumnsRoles::ArtistRole: result = d->mData[index.row()].mArtist; break; case ColumnsRoles::AlbumArtistRole: result = d->mData[index.row()].mArtist; break; case ColumnsRoles::AlbumRole: result = d->mData[index.row()].mAlbum; break; case ColumnsRoles::TrackNumberRole: result = -1; break; case ColumnsRoles::HasAlbumHeader: result = rowHasHeader(index.row()); break; case ColumnsRoles::DurationRole: break; case ColumnsRoles::DiscNumberRole: break; case ColumnsRoles::IsSingleDiscAlbumHeader: result = false; break; case ColumnsRoles::MilliSecondsDurationRole: break; case ColumnsRoles::ResourceRole: break; case ColumnsRoles::RatingRole: break; case ColumnsRoles::CountRole: break; case ColumnsRoles::ImageRole: result = QStringLiteral(""); break; case Qt::DisplayRole: result = d->mTrackData[index.row()].title(); break; case ColumnsRoles::SecondaryTextRole: result = QString(); break; case ColumnsRoles::ImageUrlRole: result = QUrl(QStringLiteral("image://icon/error")); break; case ColumnsRoles::ShadowForImageRole: result = false; break; case ColumnsRoles::TrackDataRole: MusicAudioTrack emptyTrack; result = QVariant::fromValue(emptyTrack); break; } } return result; } bool MediaPlayList::setData(const QModelIndex &index, const QVariant &value, int role) { bool modelModified = false; if (!index.isValid()) { return modelModified; } if (index.row() < 0 || index.row() >= d->mData.size()) { return modelModified; } if (role < ColumnsRoles::IsValidRole || role > ColumnsRoles::HasAlbumHeader) { return modelModified; } auto convertedRole = static_cast(role); switch(convertedRole) { case ColumnsRoles::IsPlayingRole: { modelModified = true; auto newState = static_cast(value.toInt()); d->mData[index.row()].mIsPlaying = newState; Q_EMIT dataChanged(index, index, {role}); if (!d->mCurrentTrack.isValid()) { resetCurrentTrack(); } break; } default: modelModified = false; } return modelModified; } bool MediaPlayList::removeRows(int row, int count, const QModelIndex &parent) { beginRemoveRows(parent, row, row + count - 1); bool hadAlbumHeader = false; if (rowCount() > row + count) { hadAlbumHeader = rowHasHeader(row + count); } for (int i = row, cpt = 0; cpt < count; ++i, ++cpt) { d->mData.removeAt(i); d->mTrackData.removeAt(i); } endRemoveRows(); if (!d->mCurrentTrack.isValid()) { d->mCurrentTrack = index(d->mCurrentPlayListPosition, 0); if (d->mCurrentTrack.isValid()) { notifyCurrentTrackChanged(); } if (!d->mCurrentTrack.isValid()) { Q_EMIT playListFinished(); resetCurrentTrack(); if (!d->mCurrentTrack.isValid()) { notifyCurrentTrackChanged(); } } } if (!d->mCurrentTrack.isValid() && rowCount(parent) <= row) { resetCurrentTrack(); } Q_EMIT tracksCountChanged(); if (hadAlbumHeader != rowHasHeader(row)) { Q_EMIT dataChanged(index(row, 0), index(row, 0), {ColumnsRoles::HasAlbumHeader}); if (!d->mCurrentTrack.isValid()) { resetCurrentTrack(); } } Q_EMIT persistentStateChanged(); return false; } bool MediaPlayList::moveRows(const QModelIndex &sourceParent, int sourceRow, int count, const QModelIndex &destinationParent, int destinationChild) { if (sourceParent != destinationParent) { return false; } if (!beginMoveRows(sourceParent, sourceRow, sourceRow + count - 1, destinationParent, destinationChild)) { return false; } auto firstMovedTrackHasHeader = rowHasHeader(sourceRow); auto nextTrackHasHeader = rowHasHeader(sourceRow + count); auto futureNextTrackHasHeader = rowHasHeader(destinationChild); if (sourceRow < destinationChild) { nextTrackHasHeader = rowHasHeader(sourceRow + count); } for (auto cptItem = 0; cptItem < count; ++cptItem) { if (sourceRow < destinationChild) { d->mData.move(sourceRow, destinationChild - 1); d->mTrackData.move(sourceRow, destinationChild - 1); } else { d->mData.move(sourceRow, destinationChild); d->mTrackData.move(sourceRow, destinationChild); } } endMoveRows(); if (sourceRow < destinationChild) { if (firstMovedTrackHasHeader != rowHasHeader(destinationChild - count)) { Q_EMIT dataChanged(index(destinationChild - count, 0), index(destinationChild - count, 0), {ColumnsRoles::HasAlbumHeader}); if (!d->mCurrentTrack.isValid()) { resetCurrentTrack(); } } } else { if (firstMovedTrackHasHeader != rowHasHeader(destinationChild)) { Q_EMIT dataChanged(index(destinationChild, 0), index(destinationChild, 0), {ColumnsRoles::HasAlbumHeader}); if (!d->mCurrentTrack.isValid()) { resetCurrentTrack(); } } } if (sourceRow < destinationChild) { if (nextTrackHasHeader != rowHasHeader(sourceRow)) { Q_EMIT dataChanged(index(sourceRow, 0), index(sourceRow, 0), {ColumnsRoles::HasAlbumHeader}); if (!d->mCurrentTrack.isValid()) { resetCurrentTrack(); } } } else { if (nextTrackHasHeader != rowHasHeader(sourceRow + count)) { Q_EMIT dataChanged(index(sourceRow + count, 0), index(sourceRow + count, 0), {ColumnsRoles::HasAlbumHeader}); if (!d->mCurrentTrack.isValid()) { resetCurrentTrack(); } } } if (sourceRow < destinationChild) { if (futureNextTrackHasHeader != rowHasHeader(destinationChild + count - 1)) { Q_EMIT dataChanged(index(destinationChild + count - 1, 0), index(destinationChild + count - 1, 0), {ColumnsRoles::HasAlbumHeader}); if (!d->mCurrentTrack.isValid()) { resetCurrentTrack(); } } } else { if (futureNextTrackHasHeader != rowHasHeader(destinationChild + count)) { Q_EMIT dataChanged(index(destinationChild + count, 0), index(destinationChild + count, 0), {ColumnsRoles::HasAlbumHeader}); if (!d->mCurrentTrack.isValid()) { resetCurrentTrack(); } } } Q_EMIT persistentStateChanged(); return true; } void MediaPlayList::move(int from, int to, int n) { if (from < to) { moveRows({}, from, n, {}, to + 1); } else { moveRows({}, from, n, {}, to); } } void MediaPlayList::enqueue(qulonglong newTrackId) { enqueue(MediaPlayListEntry(newTrackId)); } void MediaPlayList::enqueue(const MusicAudioTrack &newTrack) { enqueue(MediaPlayListEntry(newTrack), newTrack); } void MediaPlayList::enqueue(const MediaPlayListEntry &newEntry, const MusicAudioTrack &audioTrack) { beginInsertRows(QModelIndex(), d->mData.size(), d->mData.size()); d->mData.push_back(newEntry); if (audioTrack.isValid()) { d->mTrackData.push_back(audioTrack); } else { d->mTrackData.push_back({}); } endInsertRows(); restorePlayListPosition(); if (!d->mCurrentTrack.isValid()) { resetCurrentTrack(); } Q_EMIT tracksCountChanged(); Q_EMIT persistentStateChanged(); if (!newEntry.mIsValid) { if (newEntry.mTrackUrl.isValid()) { qDebug() << "MediaPlayList::enqueue" << "newTrackByFileNameInList" << newEntry.mTrackUrl; if (newEntry.mTrackUrl.isLocalFile()) { QFileInfo newTrackFile(newEntry.mTrackUrl.toLocalFile()); if (newTrackFile.exists()) { d->mData.last().mIsValid = true; } Q_EMIT newTrackByFileNameInList(newEntry.mTrackUrl); } } else { Q_EMIT newTrackByNameInList(newEntry.mTitle, newEntry.mArtist, newEntry.mAlbum, newEntry.mTrackNumber, newEntry.mDiscNumber); } } else { Q_EMIT newTrackByIdInList(newEntry.mId); } Q_EMIT trackHasBeenAdded(data(index(d->mData.size() - 1, 0), ColumnsRoles::TitleRole).toString(), data(index(d->mData.size() - 1, 0), ColumnsRoles::ImageRole).toUrl()); if (!newEntry.mIsValid) { Q_EMIT dataChanged(index(rowCount() - 1, 0), index(rowCount() - 1, 0), {MediaPlayList::HasAlbumHeader}); if (!d->mCurrentTrack.isValid()) { resetCurrentTrack(); } } } void MediaPlayList::enqueue(const MusicAlbum &album) { for (auto oneTrackIndex = 0; oneTrackIndex < album.tracksCount(); ++oneTrackIndex) { enqueue(album.trackFromIndex(oneTrackIndex)); } } void MediaPlayList::enqueue(const MusicArtist &artist) { enqueue(artist.name()); } void MediaPlayList::enqueue(const QString &artistName) { beginInsertRows(QModelIndex(), d->mData.size(), d->mData.size()); d->mData.push_back(MediaPlayListEntry{artistName}); d->mTrackData.push_back({}); endInsertRows(); restorePlayListPosition(); if (!d->mCurrentTrack.isValid()) { resetCurrentTrack(); } Q_EMIT tracksCountChanged(); Q_EMIT newArtistInList(artistName); Q_EMIT persistentStateChanged(); } void MediaPlayList::enqueue(const QUrl &fileName) { qDebug() << "MediaPlayList::enqueue" << fileName; enqueue(MediaPlayListEntry(fileName)); } void MediaPlayList::enqueue(const QStringList &files) { qDebug() << "MediaPlayList::enqueue" << files; for (const auto &oneFileName : files) { enqueue(QUrl::fromLocalFile(oneFileName)); } } void MediaPlayList::enqueueAndPlay(const QStringList &files) { if (files.size() > 0) { int previousTrackNumber = tracksCount(); enqueue(files); switchTo(previousTrackNumber); Q_EMIT ensurePlay(); } } void MediaPlayList::enqueue(const QList &newTrackIds) { if (newTrackIds.isEmpty()) { return; } beginInsertRows(QModelIndex(), d->mData.size(), d->mData.size() + newTrackIds.size() - 1); for (auto newTrackId : newTrackIds) { d->mData.push_back(MediaPlayListEntry{newTrackId}); d->mTrackData.push_back({}); Q_EMIT newTrackByIdInList(newTrackId); } endInsertRows(); restorePlayListPosition(); if (!d->mCurrentTrack.isValid()) { resetCurrentTrack(); } Q_EMIT tracksCountChanged(); Q_EMIT persistentStateChanged(); Q_EMIT dataChanged(index(rowCount() - 1, 0), index(rowCount() - 1, 0), {MediaPlayList::HasAlbumHeader}); } void MediaPlayList::enqueue(const QList &albums, ElisaUtils::PlayListEnqueueMode enqueueMode, ElisaUtils::PlayListEnqueueTriggerPlay triggerPlay) { if (albums.isEmpty()) { return; } auto tracksCount = 0; for (const auto &oneAlbum : albums) { for (auto oneTrackIndex = 0; oneTrackIndex < oneAlbum.tracksCount(); ++oneTrackIndex) { ++tracksCount; } } if (enqueueMode == ElisaUtils::ReplacePlayList) { clearPlayList(); } beginInsertRows(QModelIndex(), d->mData.size(), d->mData.size() + tracksCount - 1); for (const auto &oneAlbum : albums) { for (auto oneTrackIndex = 0; oneTrackIndex < oneAlbum.tracksCount(); ++oneTrackIndex) { const auto &oneTrack = oneAlbum.trackFromIndex(oneTrackIndex); d->mData.push_back(MediaPlayListEntry{oneTrack.databaseId()}); d->mTrackData.push_back(oneTrack); } } endInsertRows(); restorePlayListPosition(); if (!d->mCurrentTrack.isValid()) { resetCurrentTrack(); } Q_EMIT tracksCountChanged(); Q_EMIT persistentStateChanged(); Q_EMIT dataChanged(index(rowCount() - 1, 0), index(rowCount() - 1, 0), {MediaPlayList::HasAlbumHeader}); if (triggerPlay == ElisaUtils::TriggerPlay) { Q_EMIT ensurePlay(); } } void MediaPlayList::enqueue(const QList &tracks, ElisaUtils::PlayListEnqueueMode enqueueMode, ElisaUtils::PlayListEnqueueTriggerPlay triggerPlay) { if (tracks.isEmpty()) { return; } if (enqueueMode == ElisaUtils::ReplacePlayList) { clearPlayList(); } beginInsertRows(QModelIndex(), d->mData.size(), d->mData.size() + tracks.size() - 1); for (const auto &oneTrack : tracks) { d->mData.push_back(MediaPlayListEntry{oneTrack.databaseId()}); d->mTrackData.push_back(oneTrack); } endInsertRows(); restorePlayListPosition(); if (!d->mCurrentTrack.isValid()) { resetCurrentTrack(); } Q_EMIT tracksCountChanged(); Q_EMIT persistentStateChanged(); Q_EMIT dataChanged(index(rowCount() - 1, 0), index(rowCount() - 1, 0), {MediaPlayList::HasAlbumHeader}); if (triggerPlay == ElisaUtils::TriggerPlay) { Q_EMIT ensurePlay(); } } void MediaPlayList::enqueueArtists(const QList &artistNames, ElisaUtils::PlayListEnqueueMode enqueueMode, ElisaUtils::PlayListEnqueueTriggerPlay triggerPlay) { if (artistNames.isEmpty()) { return; } if (enqueueMode == ElisaUtils::ReplacePlayList) { clearPlayList(); } beginInsertRows(QModelIndex(), d->mData.size(), d->mData.size() + artistNames.size() - 1); for (const auto &artistName : artistNames) { d->mData.push_back(MediaPlayListEntry{artistName}); d->mTrackData.push_back({}); Q_EMIT newArtistInList(artistName); } endInsertRows(); restorePlayListPosition(); if (!d->mCurrentTrack.isValid()) { resetCurrentTrack(); } Q_EMIT tracksCountChanged(); Q_EMIT persistentStateChanged(); if (triggerPlay == ElisaUtils::TriggerPlay) { Q_EMIT ensurePlay(); } } +void MediaPlayList::enqueue(const QList &trackUrls, + ElisaUtils::PlayListEnqueueMode enqueueMode, + ElisaUtils::PlayListEnqueueTriggerPlay triggerPlay) +{ + if (trackUrls.isEmpty()) { + return; + } + + if (enqueueMode == ElisaUtils::ReplacePlayList) { + clearPlayList(); + } + + beginInsertRows(QModelIndex(), d->mData.size(), d->mData.size() + trackUrls.size() - 1); + for (const auto &oneTrackUrl : trackUrls) { + d->mData.push_back(MediaPlayListEntry{oneTrackUrl}); + d->mTrackData.push_back({}); + if (oneTrackUrl.isValid()) { + qDebug() << "MediaPlayList::enqueue" << "newTrackByFileNameInList" << oneTrackUrl; + if (oneTrackUrl.isLocalFile()) { + QFileInfo newTrackFile(oneTrackUrl.toLocalFile()); + if (newTrackFile.exists()) { + d->mData.last().mIsValid = true; + } + Q_EMIT newTrackByFileNameInList(oneTrackUrl); + } + } + } + endInsertRows(); + + restorePlayListPosition(); + if (!d->mCurrentTrack.isValid()) { + resetCurrentTrack(); + } + + Q_EMIT tracksCountChanged(); + Q_EMIT persistentStateChanged(); + + Q_EMIT dataChanged(index(rowCount() - 1, 0), index(rowCount() - 1, 0), {MediaPlayList::HasAlbumHeader}); + + if (triggerPlay == ElisaUtils::TriggerPlay) { + Q_EMIT ensurePlay(); + } +} + void MediaPlayList::replaceAndPlay(qulonglong newTrackId) { clearPlayList(); enqueue(MediaPlayListEntry(newTrackId)); Q_EMIT ensurePlay(); } void MediaPlayList::replaceAndPlay(const MusicAudioTrack &newTrack) { clearPlayList(); enqueue(newTrack); Q_EMIT ensurePlay(); } void MediaPlayList::replaceAndPlay(const MusicAlbum &album) { clearPlayList(); enqueue(album); Q_EMIT ensurePlay(); } void MediaPlayList::replaceAndPlay(const MusicArtist &artist) { clearPlayList(); enqueue(artist.name()); Q_EMIT ensurePlay(); } void MediaPlayList::replaceAndPlay(const QString &artistName) { clearPlayList(); enqueue(artistName); Q_EMIT ensurePlay(); } void MediaPlayList::replaceAndPlay(const QUrl &fileName) { clearPlayList(); enqueue(fileName); Q_EMIT ensurePlay(); } void MediaPlayList::clearPlayList() { if (d->mData.isEmpty()) { return; } beginRemoveRows({}, 0, d->mData.count() - 1); d->mData.clear(); d->mTrackData.clear(); endRemoveRows(); d->mCurrentPlayListPosition = 0; d->mCurrentTrack = QPersistentModelIndex{}; notifyCurrentTrackChanged(); Q_EMIT tracksCountChanged(); } void MediaPlayList::loadPlaylist(const QUrl &fileName) { d->mLoadPlaylist.clear(); d->mLoadPlaylist.load(fileName, "m3u"); } bool MediaPlayList::savePlaylist(const QUrl &fileName) { QMediaPlaylist savePlaylist; for (int i = 0; i < d->mData.size(); ++i) { const auto &oneTrack = d->mData.at(i); const auto &oneTrackData = d->mTrackData.at(i); if (oneTrack.mIsValid) { savePlaylist.addMedia(oneTrackData.resourceURI()); } } return savePlaylist.save(fileName, "m3u"); } QVariantMap MediaPlayList::persistentState() const { auto currentState = QVariantMap(); auto result = QList(); for (int trackIndex = 0; trackIndex < d->mData.size(); ++trackIndex) { auto oneData = QList(); const auto &oneEntry = d->mData[trackIndex]; if (oneEntry.mIsValid) { const auto &oneTrack = d->mTrackData[trackIndex]; oneData.push_back(oneTrack.title()); oneData.push_back(oneTrack.artist()); oneData.push_back(oneTrack.albumName()); oneData.push_back(QString::number(oneTrack.trackNumber())); oneData.push_back(QString::number(oneTrack.discNumber())); result.push_back(QVariant(oneData)); } } currentState[QStringLiteral("playList")] = result; currentState[QStringLiteral("currentTrack")] = d->mCurrentPlayListPosition; currentState[QStringLiteral("randomPlay")] = d->mRandomPlay; currentState[QStringLiteral("repeatPlay")] = d->mRepeatPlay; return currentState; } MusicListenersManager *MediaPlayList::musicListenersManager() const { return d->mMusicListenersManager; } int MediaPlayList::tracksCount() const { return rowCount(); } QPersistentModelIndex MediaPlayList::currentTrack() const { return d->mCurrentTrack; } int MediaPlayList::currentTrackRow() const { return d->mCurrentTrack.row(); } bool MediaPlayList::randomPlay() const { return d->mRandomPlay; } bool MediaPlayList::repeatPlay() const { return d->mRepeatPlay; } void MediaPlayList::setPersistentState(const QVariantMap &persistentStateValue) { if (d->mPersistentState == persistentStateValue) { return; } qDebug() << "MediaPlayList::setPersistentState" << persistentStateValue; d->mPersistentState = persistentStateValue; auto persistentState = d->mPersistentState[QStringLiteral("playList")].toList(); for (auto &oneData : persistentState) { auto trackData = oneData.toStringList(); if (trackData.size() != 5) { continue; } auto restoredTitle = trackData[0]; auto restoredArtist = trackData[1]; auto restoredAlbum = trackData[2]; auto restoredTrackNumber = trackData[3].toInt(); auto restoredDiscNumber = trackData[4].toInt(); enqueue({restoredTitle, restoredArtist, restoredAlbum, restoredTrackNumber, restoredDiscNumber}); } restorePlayListPosition(); restoreRandomPlay(); restoreRepeatPlay(); Q_EMIT persistentStateChanged(); } void MediaPlayList::removeSelection(QList selection) { std::sort(selection.begin(), selection.end()); std::reverse(selection.begin(), selection.end()); for (auto oneItem : selection) { removeRow(oneItem); } } void MediaPlayList::albumAdded(const QList &tracks) { for (int playListIndex = 0; playListIndex < d->mData.size(); ++playListIndex) { auto &oneEntry = d->mData[playListIndex]; if (!oneEntry.mIsArtist || oneEntry.mIsValid) { continue; } if (oneEntry.mArtist != tracks.first().artist()) { continue; } d->mTrackData[playListIndex] = tracks.first(); oneEntry.mId = tracks.first().databaseId(); oneEntry.mIsValid = true; oneEntry.mIsArtist = false; Q_EMIT dataChanged(index(playListIndex, 0), index(playListIndex, 0), {}); if (!d->mCurrentTrack.isValid()) { resetCurrentTrack(); } if (tracks.size() > 1) { beginInsertRows(QModelIndex(), playListIndex + 1, playListIndex - 1 + tracks.size()); for (int trackIndex = 1; trackIndex < tracks.size(); ++trackIndex) { d->mData.push_back(MediaPlayListEntry{tracks[trackIndex].databaseId()}); d->mTrackData.push_back(tracks[trackIndex]); } endInsertRows(); restorePlayListPosition(); if (!d->mCurrentTrack.isValid()) { resetCurrentTrack(); } Q_EMIT tracksCountChanged(); } Q_EMIT persistentStateChanged(); } } void MediaPlayList::trackChanged(const MusicAudioTrack &track) { for (int i = 0; i < d->mData.size(); ++i) { auto &oneEntry = d->mData[i]; if (!oneEntry.mIsArtist && oneEntry.mIsValid) { if (oneEntry.mTrackUrl.isValid() && track.resourceURI() != oneEntry.mTrackUrl) { continue; } if (!oneEntry.mTrackUrl.isValid() && (oneEntry.mId == 0 || track.databaseId() != oneEntry.mId)) { continue; } if (d->mTrackData[i] != track) { d->mTrackData[i] = track; Q_EMIT dataChanged(index(i, 0), index(i, 0), {}); if (!d->mCurrentTrack.isValid()) { resetCurrentTrack(); } } continue; } else if (!oneEntry.mIsArtist && !oneEntry.mIsValid && !oneEntry.mTrackUrl.isValid()) { if (track.title() != oneEntry.mTitle) { continue; } if (track.albumName() != oneEntry.mAlbum) { continue; } if (track.trackNumber() != oneEntry.mTrackNumber) { continue; } if (track.discNumber() != oneEntry.mDiscNumber) { continue; } d->mTrackData[i] = track; oneEntry.mId = track.databaseId(); oneEntry.mIsValid = true; Q_EMIT dataChanged(index(i, 0), index(i, 0), {}); if (!d->mCurrentTrack.isValid()) { resetCurrentTrack(); } break; } else if (!oneEntry.mIsArtist && !oneEntry.mIsValid && oneEntry.mTrackUrl.isValid()) { qDebug() << "MediaPlayList::trackChanged" << oneEntry << track; qDebug() << "MediaPlayList::trackChanged" << track.resourceURI() << oneEntry.mTrackUrl; if (track.resourceURI() != oneEntry.mTrackUrl) { continue; } d->mTrackData[i] = track; oneEntry.mId = track.databaseId(); oneEntry.mIsValid = true; Q_EMIT dataChanged(index(i, 0), index(i, 0), {}); restorePlayListPosition(); if (!d->mCurrentTrack.isValid()) { resetCurrentTrack(); } break; } } } void MediaPlayList::trackRemoved(qulonglong trackId) { for (int i = 0; i < d->mData.size(); ++i) { auto &oneEntry = d->mData[i]; if (oneEntry.mIsValid) { if (oneEntry.mId == trackId) { oneEntry.mIsValid = false; oneEntry.mTitle = d->mTrackData[i].title(); oneEntry.mArtist = d->mTrackData[i].artist(); oneEntry.mAlbum = d->mTrackData[i].albumName(); oneEntry.mTrackNumber = d->mTrackData[i].trackNumber(); oneEntry.mDiscNumber = d->mTrackData[i].discNumber(); Q_EMIT dataChanged(index(i, 0), index(i, 0), {}); if (!d->mCurrentTrack.isValid()) { resetCurrentTrack(); } } } } } void MediaPlayList::setMusicListenersManager(MusicListenersManager *musicListenersManager) { if (d->mMusicListenersManager == musicListenersManager) { return; } d->mMusicListenersManager = musicListenersManager; if (d->mMusicListenersManager) { d->mMusicListenersManager->subscribeForTracks(this); } Q_EMIT musicListenersManagerChanged(); } void MediaPlayList::setRandomPlay(bool value) { d->mRandomPlay = value; Q_EMIT randomPlayChanged(); } void MediaPlayList::setRepeatPlay(bool value) { d->mRepeatPlay = value; Q_EMIT repeatPlayChanged(); } void MediaPlayList::skipNextTrack() { if (!d->mCurrentTrack.isValid()) { return; } if (!d->mRandomPlay && (d->mCurrentTrack.row() >= (rowCount() - 1))) { if (!d->mRepeatPlay) { Q_EMIT playListFinished(); } if (rowCount() == 1) { d->mCurrentTrack = QPersistentModelIndex{}; notifyCurrentTrackChanged(); } resetCurrentTrack(); return; } if (d->mRandomPlay) { int randomValue = qrand(); randomValue = randomValue % (rowCount()); d->mCurrentTrack = index(randomValue, 0); } else { d->mCurrentTrack = index(d->mCurrentTrack.row() + 1, 0); } notifyCurrentTrackChanged(); } void MediaPlayList::skipPreviousTrack() { if (!d->mCurrentTrack.isValid()) { return; } if (!d->mRandomPlay && !d->mRepeatPlay && d->mCurrentTrack.row() <= 0) { return; } if (d->mRandomPlay) { int randomValue = qrand(); randomValue = randomValue % (rowCount()); d->mCurrentTrack = index(randomValue, 0); } else { if (d->mRepeatPlay) { if (d->mCurrentTrack.row() == 0) { d->mCurrentTrack = index(rowCount() - 1, 0); } else { d->mCurrentTrack = index(d->mCurrentTrack.row() - 1, 0); } } else { d->mCurrentTrack = index(d->mCurrentTrack.row() - 1, d->mCurrentTrack.column(), d->mCurrentTrack.parent()); } } notifyCurrentTrackChanged(); } void MediaPlayList::seedRandomGenerator(uint seed) { qsrand(seed); } void MediaPlayList::switchTo(int row) { if (!d->mCurrentTrack.isValid()) { return; } d->mCurrentTrack = index(row, 0); notifyCurrentTrackChanged(); } void MediaPlayList::trackInError(QUrl sourceInError, QMediaPlayer::Error playerError) { Q_UNUSED(playerError) for (int i = 0; i < d->mData.size(); ++i) { auto &oneTrack = d->mData[i]; if (oneTrack.mIsValid) { const auto &oneTrackData = d->mTrackData.at(i); if (oneTrackData.resourceURI() == sourceInError) { oneTrack.mIsValid = false; Q_EMIT dataChanged(index(i, 0), index(i, 0), {ColumnsRoles::IsValidRole}); } } } } bool MediaPlayList::rowHasHeader(int row) const { if (row >= rowCount()) { return false; } if (row < 0) { return false; } if (row - 1 < 0) { return true; } auto currentAlbumTitle = QString(); auto currentAlbumArtist = QString(); if (d->mData[row].mIsValid) { currentAlbumTitle = d->mTrackData[row].albumName(); currentAlbumArtist = d->mTrackData[row].albumArtist(); } else { currentAlbumTitle = d->mData[row].mAlbum; currentAlbumArtist = d->mData[row].mArtist; } auto previousAlbumTitle = QString(); auto previousAlbumArtist = QString(); if (d->mData[row - 1].mIsValid) { previousAlbumTitle = d->mTrackData[row - 1].albumName(); previousAlbumArtist = d->mTrackData[row - 1].albumArtist(); } else { previousAlbumTitle = d->mData[row - 1].mAlbum; previousAlbumArtist = d->mData[row - 1].mArtist; } if (currentAlbumTitle == previousAlbumTitle && currentAlbumArtist == previousAlbumArtist) { return false; } return true; } void MediaPlayList::loadPlayListLoaded() { clearPlayList(); for (int i = 0; i < d->mLoadPlaylist.mediaCount(); ++i) { enqueue(d->mLoadPlaylist.media(i).canonicalUrl()); } restorePlayListPosition(); restoreRandomPlay(); restoreRepeatPlay(); Q_EMIT persistentStateChanged(); d->mLoadPlaylist.clear(); Q_EMIT playListLoaded(); } void MediaPlayList::loadPlayListLoadFailed() { d->mLoadPlaylist.clear(); Q_EMIT playListLoadFailed(); } void MediaPlayList::resetCurrentTrack() { for(int row = 0; row < rowCount(); ++row) { auto candidateTrack = index(row, 0); if (candidateTrack.isValid() && candidateTrack.data(ColumnsRoles::IsValidRole).toBool()) { d->mCurrentTrack = candidateTrack; notifyCurrentTrackChanged(); break; } } } void MediaPlayList::notifyCurrentTrackChanged() { Q_EMIT currentTrackChanged(d->mCurrentTrack); Q_EMIT currentTrackRowChanged(); bool currentTrackIsValid = d->mCurrentTrack.isValid(); if (currentTrackIsValid) { d->mCurrentPlayListPosition = d->mCurrentTrack.row(); } } void MediaPlayList::restorePlayListPosition() { auto playerCurrentTrack = d->mPersistentState.find(QStringLiteral("currentTrack")); if (playerCurrentTrack != d->mPersistentState.end()) { auto newIndex = index(playerCurrentTrack->toInt(), 0); if (newIndex.isValid() && (newIndex != d->mCurrentTrack)) { d->mCurrentTrack = newIndex; notifyCurrentTrackChanged(); if (d->mCurrentTrack.isValid()) { d->mPersistentState.erase(playerCurrentTrack); } } } } void MediaPlayList::restoreRandomPlay() { auto randomPlayStoredValue = d->mPersistentState.find(QStringLiteral("randomPlay")); if (randomPlayStoredValue != d->mPersistentState.end()) { setRandomPlay(randomPlayStoredValue->toBool()); d->mPersistentState.erase(randomPlayStoredValue); } } void MediaPlayList::restoreRepeatPlay() { auto repeatPlayStoredValue = d->mPersistentState.find(QStringLiteral("repeatPlay")); if (repeatPlayStoredValue != d->mPersistentState.end()) { setRepeatPlay(repeatPlayStoredValue->toBool()); d->mPersistentState.erase(repeatPlayStoredValue); } } QDebug operator<<(QDebug stream, const MediaPlayListEntry &data) { stream << data.mTitle << data.mAlbum << data.mArtist << data.mTrackUrl << data.mTrackNumber << data.mDiscNumber << data.mId << data.mIsValid; return stream; } #include "moc_mediaplaylist.cpp" diff --git a/src/mediaplaylist.h b/src/mediaplaylist.h index 5c1ebaac..dc18cc53 100644 --- a/src/mediaplaylist.h +++ b/src/mediaplaylist.h @@ -1,340 +1,344 @@ /* * Copyright 2015-2017 Matthieu Gallien * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public * License as published by the Free Software Foundation; either * version 3 of the License, or (at your option) any later version. * * This library 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 * Library General Public License for more details. * * You should have received a copy of the GNU Library General Public License * along with this library; see the file COPYING.LIB. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #ifndef MEDIAPLAYLIST_H #define MEDIAPLAYLIST_H #include "elisaLib_export.h" #include "musicaudiotrack.h" #include "musicalbum.h" #include "musicartist.h" #include "elisautils.h" #include #include #include #include class MediaPlayListPrivate; class DatabaseInterface; class MusicListenersManager; class MediaPlayListEntry; class QDebug; class ELISALIB_EXPORT MediaPlayList : public QAbstractListModel { Q_OBJECT Q_PROPERTY(QVariantMap persistentState READ persistentState WRITE setPersistentState NOTIFY persistentStateChanged) Q_PROPERTY(MusicListenersManager* musicListenersManager READ musicListenersManager WRITE setMusicListenersManager NOTIFY musicListenersManagerChanged) Q_PROPERTY(int tracksCount READ tracksCount NOTIFY tracksCountChanged) Q_PROPERTY(QPersistentModelIndex currentTrack READ currentTrack NOTIFY currentTrackChanged) Q_PROPERTY(int currentTrackRow READ currentTrackRow NOTIFY currentTrackRowChanged) Q_PROPERTY(bool randomPlay READ randomPlay WRITE setRandomPlay NOTIFY randomPlayChanged) Q_PROPERTY(bool repeatPlay READ repeatPlay WRITE setRepeatPlay NOTIFY repeatPlayChanged) public: enum ColumnsRoles { IsValidRole = Qt::UserRole + 1, TitleRole, DurationRole, MilliSecondsDurationRole, TrackDataRole, ArtistRole, AlbumArtistRole, AlbumRole, TrackNumberRole, DiscNumberRole, RatingRole, GenreRole, LyricistRole, ComposerRole, CommentRole, YearRole, ChannelsRole, BitRateRole, SampleRateRole, ImageRole, ResourceRole, CountRole, IsPlayingRole, HasAlbumHeader, IsSingleDiscAlbumHeader, SecondaryTextRole, ImageUrlRole, ShadowForImageRole, }; Q_ENUM(ColumnsRoles) enum PlayState { NotPlaying, IsPlaying, IsPaused, }; Q_ENUM(PlayState) explicit MediaPlayList(QObject *parent = nullptr); ~MediaPlayList() override; int rowCount(const QModelIndex &parent = QModelIndex()) const override; QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override; bool setData(const QModelIndex &index, const QVariant &value, int role = Qt::EditRole) override; QHash roleNames() const override; Q_INVOKABLE bool removeRows(int row, int count, const QModelIndex &parent = QModelIndex()) override; Q_INVOKABLE bool moveRows(const QModelIndex &sourceParent, int sourceRow, int count, const QModelIndex &destinationParent, int destinationChild) override; Q_INVOKABLE void move(int from, int to, int n); Q_INVOKABLE void clearPlayList(); Q_INVOKABLE bool savePlaylist(const QUrl &fileName); QVariantMap persistentState() const; MusicListenersManager* musicListenersManager() const; int tracksCount() const; QPersistentModelIndex currentTrack() const; int currentTrackRow() const; bool randomPlay() const; bool repeatPlay() const; Q_SIGNALS: void newTrackByNameInList(const QString &title, const QString &artist, const QString &album, int trackNumber, int discNumber); void newTrackByFileNameInList(const QUrl &fileName); void newTrackByIdInList(qulonglong newTrackId); void newArtistInList(const QString &artist); void trackHasBeenAdded(const QString &title, const QUrl &image); void persistentStateChanged(); void musicListenersManagerChanged(); void tracksCountChanged(); void currentTrackChanged(QPersistentModelIndex currentTrack); void currentTrackRowChanged(); void randomPlayChanged(); void repeatPlayChanged(); void playListFinished(); void playListLoaded(); void playListLoadFailed(); void ensurePlay(); public Q_SLOTS: void setPersistentState(const QVariantMap &persistentState); void removeSelection(QList selection); void albumAdded(const QList &tracks); void trackChanged(const MusicAudioTrack &track); void trackRemoved(qulonglong trackId); void setMusicListenersManager(MusicListenersManager* musicListenersManager); void setRandomPlay(bool value); void setRepeatPlay(bool value); void skipNextTrack(); void skipPreviousTrack(); void seedRandomGenerator(uint seed); void switchTo(int row); void loadPlaylist(const QUrl &fileName); void enqueue(qulonglong newTrackId); void enqueue(const MusicAudioTrack &newTrack); void enqueue(const MediaPlayListEntry &newEntry, const MusicAudioTrack &audioTrack = {}); void enqueue(const MusicAlbum &album); void enqueue(const MusicArtist &artist); void enqueue(const QString &artistName); void enqueue(const QUrl &fileName); void enqueue(const QStringList &files); void enqueue(const QList &newTrackId); void enqueue(const QList &albums, ElisaUtils::PlayListEnqueueMode enqueueMode, ElisaUtils::PlayListEnqueueTriggerPlay triggerPlay); void enqueue(const QList &tracks, ElisaUtils::PlayListEnqueueMode enqueueMode, ElisaUtils::PlayListEnqueueTriggerPlay triggerPlay); void enqueueArtists(const QList &artistName, ElisaUtils::PlayListEnqueueMode enqueueMode, ElisaUtils::PlayListEnqueueTriggerPlay triggerPlay); + void enqueue(const QList &tracks, + ElisaUtils::PlayListEnqueueMode enqueueMode, + ElisaUtils::PlayListEnqueueTriggerPlay triggerPlay); + void enqueueAndPlay(const QStringList &files); void replaceAndPlay(qulonglong newTrackId); void replaceAndPlay(const MusicAudioTrack &newTrack); void replaceAndPlay(const MusicAlbum &album); void replaceAndPlay(const MusicArtist &artist); void replaceAndPlay(const QString &artistName); void replaceAndPlay(const QUrl &fileName); void trackInError(QUrl sourceInError, QMediaPlayer::Error playerError); private Q_SLOTS: void loadPlayListLoaded(); void loadPlayListLoadFailed(); private: bool rowHasHeader(int row) const; void resetCurrentTrack(); void notifyCurrentTrackChanged(); void restorePlayListPosition(); void restoreRandomPlay(); void restoreRepeatPlay(); std::unique_ptr d; }; class MediaPlayListEntry { public: MediaPlayListEntry() { } explicit MediaPlayListEntry(qulonglong id) : mId(id), mIsValid(true) { } MediaPlayListEntry(QString title, QString artist, QString album, int trackNumber, int discNumber) : mTitle(std::move(title)), mAlbum(std::move(album)), mArtist(std::move(artist)), mTrackNumber(trackNumber), mDiscNumber(discNumber) { } explicit MediaPlayListEntry(const MusicAudioTrack &track) : mTitle(track.title()), mAlbum(track.albumName()), mTrackNumber(track.trackNumber()), mDiscNumber(track.discNumber()), mId(track.databaseId()), mIsValid(true) { } explicit MediaPlayListEntry(QString artist) : mArtist(std::move(artist)), mIsArtist(true) { } explicit MediaPlayListEntry(QUrl fileName) : mTrackUrl(std::move(fileName)) { } QString mTitle; QString mAlbum; QString mArtist; QUrl mTrackUrl; int mTrackNumber = -1; int mDiscNumber = -1; qulonglong mId = 0; bool mIsValid = false; bool mIsArtist = false; MediaPlayList::PlayState mIsPlaying = MediaPlayList::NotPlaying; }; QDebug operator<<(QDebug stream, const MediaPlayListEntry &data); #endif // MEDIAPLAYLIST_H