diff --git a/src/audiowrapper.h b/src/audiowrapper.h --- a/src/audiowrapper.h +++ b/src/audiowrapper.h @@ -142,6 +142,8 @@ void setPosition(qint64 position); + void savePosition(qint64 position); + void play(); void pause(); diff --git a/src/audiowrapper_libvlc.cpp b/src/audiowrapper_libvlc.cpp --- a/src/audiowrapper_libvlc.cpp +++ b/src/audiowrapper_libvlc.cpp @@ -259,17 +259,22 @@ } if (d->mMediaDuration == -1 || d->mMediaDuration == 0) { - if (!d->mHasSavedPosition) { - d->mHasSavedPosition = true; - d->mSavedPosition = position; - qDebug() << "AudioWrapper::setPosition" << "restore old position" << d->mSavedPosition; - } + savePosition(position); return; } libvlc_media_player_set_position(d->mPlayer, static_cast(position) / d->mMediaDuration); } +void AudioWrapper::savePosition(qint64 position) +{ + if (!d->mHasSavedPosition) { + d->mHasSavedPosition = true; + d->mSavedPosition = position; + qDebug() << "AudioWrapper::setPosition" << "restore old position" << d->mSavedPosition; + } +} + void AudioWrapper::play() { if (!d->mPlayer) { diff --git a/src/elisaapplication.cpp b/src/elisaapplication.cpp --- a/src/elisaapplication.cpp +++ b/src/elisaapplication.cpp @@ -353,6 +353,7 @@ QObject::connect(d->mAudioControl.get(), &ManageAudioPlayer::playerPause, d->mAudioWrapper.get(), &AudioWrapper::pause); QObject::connect(d->mAudioControl.get(), &ManageAudioPlayer::playerStop, d->mAudioWrapper.get(), &AudioWrapper::stop); QObject::connect(d->mAudioControl.get(), &ManageAudioPlayer::seek, d->mAudioWrapper.get(), &AudioWrapper::seek); + QObject::connect(d->mAudioControl.get(), &ManageAudioPlayer::savePositionInAudioWrapper, d->mAudioWrapper.get(), &AudioWrapper::savePosition); QObject::connect(d->mAudioControl.get(), &ManageAudioPlayer::skipNextTrack, d->mMediaPlayList.get(), &MediaPlayList::skipNextTrack); QObject::connect(d->mAudioControl.get(), &ManageAudioPlayer::sourceInError, d->mMediaPlayList.get(), &MediaPlayList::trackInError); QObject::connect(d->mAudioControl.get(), &ManageAudioPlayer::sourceInError, d->mMusicManager.get(), &MusicListenersManager::playBackError); @@ -363,6 +364,9 @@ QObject::connect(d->mMediaPlayList.get(), &MediaPlayList::ensurePlay, d->mAudioControl.get(), &ManageAudioPlayer::ensurePlay); QObject::connect(d->mMediaPlayList.get(), &MediaPlayList::playListFinished, d->mAudioControl.get(), &ManageAudioPlayer::playListFinished); QObject::connect(d->mMediaPlayList.get(), &MediaPlayList::currentTrackChanged, d->mAudioControl.get(), &ManageAudioPlayer::setCurrentTrack); + QObject::connect(d->mMediaPlayList.get(), &MediaPlayList::clearPlayListPlayer, d->mAudioControl.get(), &ManageAudioPlayer::saveForUndoClearPlaylist); + QObject::connect(d->mMediaPlayList.get(), &MediaPlayList::undoClearPlayListPlayer, d->mAudioControl.get(), &ManageAudioPlayer::restoreForUndoClearPlaylist); + QObject::connect(d->mAudioWrapper.get(), &AudioWrapper::playbackStateChanged, d->mAudioControl.get(), &ManageAudioPlayer::setPlayerPlaybackState); diff --git a/src/manageaudioplayer.h b/src/manageaudioplayer.h --- a/src/manageaudioplayer.h +++ b/src/manageaudioplayer.h @@ -187,6 +187,8 @@ void seek(qint64 position); + void savePositionInAudioWrapper(qint64 position); + void titleRoleChanged(); void artistNameRoleChanged(); @@ -203,6 +205,10 @@ void setCurrentTrack(const QPersistentModelIndex ¤tTrack); + void saveForUndoClearPlaylist(); + + void restoreForUndoClearPlaylist(); + void setPlayListModel(QAbstractItemModel* aPlayListModel); void setUrlRole(int value); @@ -295,6 +301,10 @@ QVariantMap mPersistentState; + bool mUndoPlayingState = false; + + qint64 mUndoPlayerPosition = 0; + }; #endif // MANAGEAUDIOPLAYER_H diff --git a/src/manageaudioplayer.cpp b/src/manageaudioplayer.cpp --- a/src/manageaudioplayer.cpp +++ b/src/manageaudioplayer.cpp @@ -166,6 +166,20 @@ } } +void ManageAudioPlayer::saveForUndoClearPlaylist(){ + mUndoPlayingState = mPlayingState; + + mUndoPlayerPosition = mPlayerPosition; + Q_EMIT savePositionInAudioWrapper(mUndoPlayerPosition); +} + +void ManageAudioPlayer::restoreForUndoClearPlaylist(){ + mPlayerPosition = mUndoPlayerPosition; + Q_EMIT seek(mPlayerPosition); + + mPlayingState = mUndoPlayingState; +} + void ManageAudioPlayer::setPlayListModel(QAbstractItemModel *aPlayListModel) { if (mPlayListModel == aPlayListModel) { diff --git a/src/mediaplaylist.h b/src/mediaplaylist.h --- a/src/mediaplaylist.h +++ b/src/mediaplaylist.h @@ -54,6 +54,9 @@ READ tracksCount NOTIFY tracksCountChanged) + Q_PROPERTY(int displayUndoInline + READ displayUndoInline) + Q_PROPERTY(QPersistentModelIndex currentTrack READ currentTrack NOTIFY currentTrackChanged) @@ -162,6 +165,8 @@ int tracksCount() const; + bool displayUndoInline() const; + QPersistentModelIndex currentTrack() const; int currentTrackRow() const; @@ -186,6 +191,10 @@ void currentTrackChanged(QPersistentModelIndex currentTrack); + void clearPlayListPlayer(); + + void undoClearPlayListPlayer(); + void currentTrackRowChanged(); void randomPlayChanged(); @@ -256,6 +265,8 @@ void trackInError(const QUrl &sourceInError, QMediaPlayer::Error playerError); + Q_SCRIPTABLE void undoClearPlayList(); + private Q_SLOTS: void loadPlayListLoaded(); @@ -284,8 +295,13 @@ void enqueueMultipleEntries(const ElisaUtils::EntryDataList &entriesData, ElisaUtils::PlayListEntryType type); + void enqueueCommon(); + + void copyD(); + std::unique_ptr d; + std::unique_ptr dOld; }; class MediaPlayListEntry @@ -297,9 +313,9 @@ explicit MediaPlayListEntry(qulonglong id) : mId(id), mIsValid(true) { } - MediaPlayListEntry(QString title, QString artist, QString album, int trackNumber, int discNumber) + MediaPlayListEntry(QString title, QString artist, QString album, int trackNumber, int discNumber, ElisaUtils::PlayListEntryType entryType = ElisaUtils::Unknown) : mTitle(std::move(title)), mAlbum(std::move(album)), mArtist(std::move(artist)), - mTrackNumber(trackNumber), mDiscNumber(discNumber) { + mTrackNumber(trackNumber), mDiscNumber(discNumber), mEntryType(entryType) { } explicit MediaPlayListEntry(const MusicAudioTrack &track) diff --git a/src/mediaplaylist.cpp b/src/mediaplaylist.cpp --- a/src/mediaplaylist.cpp +++ b/src/mediaplaylist.cpp @@ -53,9 +53,11 @@ bool mRepeatPlay = false; + bool mDisplayUndoInline = false; + }; -MediaPlayList::MediaPlayList(QObject *parent) : QAbstractListModel(parent), d(new MediaPlayListPrivate) +MediaPlayList::MediaPlayList(QObject *parent) : QAbstractListModel(parent), d(new MediaPlayListPrivate), dOld(new MediaPlayListPrivate) { connect(&d->mLoadPlaylist, &QMediaPlaylist::loaded, this, &MediaPlayList::loadPlayListLoaded); connect(&d->mLoadPlaylist, &QMediaPlaylist::loadFailed, this, &MediaPlayList::loadPlayListLoadFailed); @@ -309,6 +311,8 @@ void MediaPlayList::enqueueRestoredEntry(const MediaPlayListEntry &newEntry) { + enqueueCommon(); + beginInsertRows(QModelIndex(), d->mData.size(), d->mData.size()); d->mData.push_back(newEntry); d->mTrackData.push_back({}); @@ -355,8 +359,13 @@ void MediaPlayList::enqueueArtist(const QString &artistName) { + enqueueCommon(); + + auto newEntry = MediaPlayListEntry{artistName}; + newEntry.mEntryType = ElisaUtils::Artist; + beginInsertRows(QModelIndex(), d->mData.size(), d->mData.size()); - d->mData.push_back(MediaPlayListEntry{artistName}); + d->mData.push_back(newEntry); d->mTrackData.push_back({}); endInsertRows(); @@ -366,15 +375,18 @@ } Q_EMIT tracksCountChanged(); - Q_EMIT newEntryInList(0, artistName, ElisaUtils::Artist); + Q_EMIT newEntryInList(0, artistName, newEntry.mEntryType); Q_EMIT persistentStateChanged(); } void MediaPlayList::enqueueFilesList(const ElisaUtils::EntryDataList &newEntries) { + enqueueCommon(); + beginInsertRows(QModelIndex(), d->mData.size(), d->mData.size() + newEntries.size() - 1); for (const auto &oneTrackUrl : newEntries) { auto newEntry = MediaPlayListEntry(QUrl::fromLocalFile(std::get<1>(oneTrackUrl))); + newEntry.mEntryType = ElisaUtils::FileName; d->mData.push_back(newEntry); d->mTrackData.push_back({}); if (newEntry.mTrackUrl.isValid()) { @@ -385,7 +397,7 @@ if (newTrackFile.exists()) { d->mData.last().mIsValid = true; } - Q_EMIT newEntryInList(0, entryString, ElisaUtils::FileName); + Q_EMIT newEntryInList(0, entryString, newEntry.mEntryType); } } } @@ -404,11 +416,14 @@ void MediaPlayList::enqueueTracksListById(const ElisaUtils::EntryDataList &newEntries) { + enqueueCommon(); + beginInsertRows(QModelIndex(), d->mData.size(), d->mData.size() + newEntries.size() - 1); for (const auto &newTrack : newEntries) { - d->mData.push_back(MediaPlayListEntry{std::get<0>(newTrack)}); + auto newMediaPlayListEntry = MediaPlayListEntry{std::get<0>(newTrack), std::get<1>(newTrack), ElisaUtils::Track}; + d->mData.push_back(newMediaPlayListEntry); d->mTrackData.push_back({}); - Q_EMIT newEntryInList(std::get<0>(newTrack), std::get<1>(newTrack), ElisaUtils::Track); + Q_EMIT newEntryInList(newMediaPlayListEntry.mId, newMediaPlayListEntry.mTitle.toString(), newMediaPlayListEntry.mEntryType); } endInsertRows(); @@ -425,6 +440,8 @@ void MediaPlayList::enqueueOneEntry(const ElisaUtils::EntryData &entryData, ElisaUtils::PlayListEntryType type) { + enqueueCommon(); + beginInsertRows(QModelIndex(), d->mData.size(), d->mData.size()); d->mData.push_back(MediaPlayListEntry{std::get<0>(entryData), std::get<1>(entryData), type}); d->mTrackData.push_back({}); @@ -437,6 +454,8 @@ void MediaPlayList::enqueueMultipleEntries(const ElisaUtils::EntryDataList &entriesData, ElisaUtils::PlayListEntryType type) { + enqueueCommon(); + beginInsertRows(QModelIndex(), d->mData.size(), d->mData.size() + entriesData.size() - 1); for (const auto &entryData : entriesData) { d->mData.push_back(MediaPlayListEntry{std::get<0>(entryData), std::get<1>(entryData), type}); @@ -461,6 +480,9 @@ return; } + Q_EMIT clearPlayListPlayer(); + this->copyD(); + beginRemoveRows({}, 0, d->mData.count() - 1); d->mData.clear(); d->mTrackData.clear(); @@ -470,8 +492,77 @@ d->mCurrentTrack = QPersistentModelIndex{}; notifyCurrentTrackChanged(); + d->mDisplayUndoInline= true; + + Q_EMIT tracksCountChanged(); + Q_EMIT persistentStateChanged(); +} + +void MediaPlayList::undoClearPlayList() +{ + beginInsertRows(QModelIndex(), d->mData.size(), d->mData.size() + dOld->mData.size() - 1); + for (auto &newTrack : dOld->mData) { + d->mData.push_back(newTrack); + d->mTrackData.push_back({}); + + if (ElisaUtils::FileName == newTrack.mEntryType && newTrack.mTrackUrl.isValid()) { + auto entryURL = newTrack.mTrackUrl.toUrl(); + if (entryURL.isLocalFile()) { + auto entryString = entryURL.toLocalFile(); + QFileInfo newTrackFile(entryString); + if (newTrackFile.exists()) { + d->mData.last().mIsValid = true; + } + Q_EMIT newEntryInList(0, entryString, newTrack.mEntryType); + } + } + else if(ElisaUtils::Artist == newTrack.mEntryType){ + Q_EMIT newEntryInList(0, newTrack.mArtist.toString(), newTrack.mEntryType); + } + else{ + Q_EMIT newEntryInList(newTrack.mId, newTrack.mTitle.toString(), newTrack.mEntryType); + } + } + endInsertRows(); + + d->mMusicListenersManager = dOld->mMusicListenersManager; + d->mPersistentState = dOld->mPersistentState; + d->mCurrentPlayListPosition = dOld->mCurrentPlayListPosition; + d->mRandomPlay = dOld->mRandomPlay; + d->mRepeatPlay = dOld->mRepeatPlay; + + auto candidateTrack = index(dOld->mCurrentPlayListPosition, 0); + + if (candidateTrack.isValid() && candidateTrack.data(ColumnsRoles::IsValidRole).toBool()) { + d->mCurrentTrack = candidateTrack; + notifyCurrentTrackChanged(); + } + + d->mDisplayUndoInline = false; + Q_EMIT tracksCountChanged(); Q_EMIT persistentStateChanged(); + + Q_EMIT dataChanged(index(rowCount() - 1, 0), index(rowCount() - 1, 0), {MediaPlayList::IsPlayingRole}); + + Q_EMIT undoClearPlayListPlayer(); +} + +void MediaPlayList::enqueueCommon(){ + d->mDisplayUndoInline = false; +} + + +void MediaPlayList::copyD() +{ + dOld->mData = d->mData; + dOld->mTrackData = d->mTrackData; + dOld->mMusicListenersManager = d->mMusicListenersManager; + dOld->mCurrentTrack = d->mCurrentTrack; + dOld->mPersistentState = d->mPersistentState; + dOld->mCurrentPlayListPosition = d->mCurrentPlayListPosition; + dOld->mRandomPlay = d->mRandomPlay; + dOld->mRepeatPlay = d->mRepeatPlay; } void MediaPlayList::loadPlaylist(const QUrl &fileName) @@ -504,6 +595,8 @@ ElisaUtils::PlayListEnqueueMode enqueueMode, ElisaUtils::PlayListEnqueueTriggerPlay triggerPlay) { + enqueueCommon(); + if (enqueueMode == ElisaUtils::ReplacePlayList) { clearPlayList(); } @@ -535,6 +628,8 @@ ElisaUtils::PlayListEnqueueMode enqueueMode, ElisaUtils::PlayListEnqueueTriggerPlay triggerPlay) { + enqueueCommon(); + if (newEntries.isEmpty()) { return; } @@ -598,6 +693,7 @@ oneData.push_back(oneTrack.album()); oneData.push_back(QString::number(oneTrack.trackNumber())); oneData.push_back(QString::number(oneTrack.discNumber())); + oneData.push_back(QString::number(oneEntry.mEntryType)); result.push_back(QVariant(oneData)); } @@ -621,6 +717,11 @@ return rowCount(); } +bool MediaPlayList::displayUndoInline() const +{ + return d->mDisplayUndoInline; +} + QPersistentModelIndex MediaPlayList::currentTrack() const { return d->mCurrentTrack; @@ -655,7 +756,7 @@ for (auto &oneData : persistentState) { auto trackData = oneData.toStringList(); - if (trackData.size() != 5) { + if (trackData.size() != 6) { continue; } @@ -665,7 +766,9 @@ auto restoredTrackNumber = trackData[3].toInt(); auto restoredDiscNumber = trackData[4].toInt(); - enqueueRestoredEntry({restoredTitle, restoredArtist, restoredAlbum, restoredTrackNumber, restoredDiscNumber}); + ElisaUtils::PlayListEntryType mEntryType = static_cast(trackData[5].toInt()); + + enqueueRestoredEntry({restoredTitle, restoredArtist, restoredAlbum, restoredTrackNumber, restoredDiscNumber, mEntryType}); } restorePlayListPosition(); @@ -717,7 +820,9 @@ 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()}); + auto newEntry = MediaPlayListEntry{tracks[trackIndex]}; + newEntry.mEntryType = ElisaUtils::Track; + d->mData.push_back(newEntry); d->mTrackData.push_back(tracks[trackIndex]); } endInsertRows(); diff --git a/src/qml/MediaPlayListView.qml b/src/qml/MediaPlayListView.qml --- a/src/qml/MediaPlayListView.qml +++ b/src/qml/MediaPlayListView.qml @@ -21,6 +21,7 @@ import QtQuick.Layouts 1.1 import QtQuick.Window 2.2 import Qt.labs.platform 1.0 as PlatformDialog +import org.kde.kirigami 2.5 as Kirigami import org.kde.elisa 1.0 FocusScope { @@ -211,6 +212,26 @@ Item { Layout.fillHeight: true } + Kirigami.InlineMessage { + visible: elisa.mediaPlayList ? elisa.mediaPlayList.tracksCount === 0 && elisa.mediaPlayList.displayUndoInline : false + text: i18nc("Playlist cleared", "Playlist cleared") + type: Kirigami.MessageType.Information + showCloseButton: true + Layout.topMargin: 5 + Layout.fillWidth: true + Layout.rightMargin: elisaTheme.layoutHorizontalMargin + Layout.leftMargin: elisaTheme.layoutHorizontalMargin + + + actions: [ + Kirigami.Action { + text: i18nc("Undo", "Undo") + icon.name: "dialog-cancel" + onTriggered: elisa.mediaPlayList.undoClearPlayList() + } + ] + } + } PlayListBasicView {