diff --git a/src/audiowrapper.h b/src/audiowrapper.h --- a/src/audiowrapper.h +++ b/src/audiowrapper.h @@ -142,6 +142,10 @@ void setPosition(qint64 position); + void saveUndoPosition(qint64 position); + + void restoreUndoPosition(); + void play(); void pause(); @@ -163,6 +167,7 @@ void playerVolumeChanged(); private: + void savePosition(qint64 position); void playerStateSignalChanges(QMediaPlayer::State newState); diff --git a/src/audiowrapper_libvlc.cpp b/src/audiowrapper_libvlc.cpp --- a/src/audiowrapper_libvlc.cpp +++ b/src/audiowrapper_libvlc.cpp @@ -57,6 +57,8 @@ qint64 mSavedPosition = 0.0; + qint64 mUndoSavedPosition = 0.0; + qint64 mPreviousPosition = 0; QMediaPlayer::Error mError = QMediaPlayer::NoError; @@ -259,17 +261,33 @@ } 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::saveUndoPosition(qint64 position) +{ + d->mUndoSavedPosition = position; +} + +void AudioWrapper::restoreUndoPosition() +{ + d->mHasSavedPosition = true; + d->mSavedPosition = d->mUndoSavedPosition; +} + 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,8 @@ 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::saveUndoPositionInAudioWrapper, d->mAudioWrapper.get(), &AudioWrapper::saveUndoPosition); + QObject::connect(d->mAudioControl.get(), &ManageAudioPlayer::restoreUndoPositionInAudioWrapper, d->mAudioWrapper.get(), &AudioWrapper::restoreUndoPosition); 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 +365,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,10 @@ void seek(qint64 position); + void saveUndoPositionInAudioWrapper(qint64 position); + + void restoreUndoPositionInAudioWrapper(); + void titleRoleChanged(); void artistNameRoleChanged(); @@ -203,6 +207,10 @@ void setCurrentTrack(const QPersistentModelIndex ¤tTrack); + void saveForUndoClearPlaylist(); + + void restoreForUndoClearPlaylist(); + void setPlayListModel(QAbstractItemModel* aPlayListModel); void setUrlRole(int value); @@ -295,6 +303,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,21 @@ } } +void ManageAudioPlayer::saveForUndoClearPlaylist(){ + mUndoPlayingState = mPlayingState; + + mUndoPlayerPosition = mPlayerPosition; + Q_EMIT saveUndoPositionInAudioWrapper(mUndoPlayerPosition); +} + +void ManageAudioPlayer::restoreForUndoClearPlaylist(){ + mPlayerPosition = mUndoPlayerPosition; + Q_EMIT seek(mPlayerPosition); + + mPlayingState = mUndoPlayingState; + Q_EMIT restoreUndoPositionInAudioWrapper(); +} + 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 @@ -162,6 +162,8 @@ int tracksCount() const; + bool displayUndoInline() const; + QPersistentModelIndex currentTrack() const; int currentTrackRow() const; @@ -171,6 +173,9 @@ bool repeatPlay() const; Q_SIGNALS: + void displayUndoInline(); + + void hideUndoInline(); void newTrackByNameInList(const QString &title, const QString &artist, const QString &album, int trackNumber, int discNumber); @@ -186,6 +191,10 @@ void currentTrackChanged(QPersistentModelIndex currentTrack); + void clearPlayListPlayer(); + + void undoClearPlayListPlayer(); + void currentTrackRowChanged(); void randomPlayChanged(); @@ -256,13 +265,18 @@ void trackInError(const QUrl &sourceInError, QMediaPlayer::Error playerError); + Q_SCRIPTABLE void undoClearPlayList(); + private Q_SLOTS: void loadPlayListLoaded(); void loadPlayListLoadFailed(); private: + void displayOrHideUndoInline(bool value); + + void clearPlayList(bool prepareUndo); void resetCurrentTrack(); @@ -284,8 +298,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 +316,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 mForceUndo = 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}); @@ -455,12 +474,17 @@ enqueue(newEntry, databaseIdType, ElisaUtils::PlayListEnqueueMode::ReplacePlayList, ElisaUtils::PlayListEnqueueTriggerPlay::TriggerPlay); } -void MediaPlayList::clearPlayList() +void MediaPlayList::clearPlayList(bool prepareUndo) { if (d->mData.isEmpty()) { return; } + if(prepareUndo){ + Q_EMIT clearPlayListPlayer(); + this->copyD(); + } + beginRemoveRows({}, 0, d->mData.count() - 1); d->mData.clear(); d->mTrackData.clear(); @@ -470,10 +494,84 @@ d->mCurrentTrack = QPersistentModelIndex{}; notifyCurrentTrackChanged(); + displayOrHideUndoInline(true); Q_EMIT tracksCountChanged(); Q_EMIT persistentStateChanged(); } +void MediaPlayList::clearPlayList() +{ + this->clearPlayList(true); +} + +void MediaPlayList::undoClearPlayList() +{ + clearPlayList(false); + + 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(); + } + + Q_EMIT tracksCountChanged(); + Q_EMIT persistentStateChanged(); + + Q_EMIT dataChanged(index(rowCount() - 1, 0), index(rowCount() - 1, 0), {MediaPlayList::IsPlayingRole}); + displayOrHideUndoInline(false); + Q_EMIT undoClearPlayListPlayer(); +} + +void MediaPlayList::enqueueCommon() +{ + displayOrHideUndoInline(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) { d->mLoadPlaylist.clear(); @@ -504,10 +602,17 @@ ElisaUtils::PlayListEnqueueMode enqueueMode, ElisaUtils::PlayListEnqueueTriggerPlay triggerPlay) { + + if (enqueueMode == ElisaUtils::ReplacePlayList) { + if(d->mData.size()>0){ + d->mForceUndo = true; + } clearPlayList(); } + enqueueCommon(); + switch (databaseIdType) { case ElisaUtils::Album: @@ -528,6 +633,10 @@ if (triggerPlay == ElisaUtils::TriggerPlay) { Q_EMIT ensurePlay(); } + + if (enqueueMode == ElisaUtils::ReplacePlayList) { + d->mForceUndo = false; + } } void MediaPlayList::enqueue(const ElisaUtils::EntryDataList &newEntries, @@ -540,9 +649,14 @@ } if (enqueueMode == ElisaUtils::ReplacePlayList) { + if(d->mData.size()>0){ + d->mForceUndo = true; + } clearPlayList(); } + enqueueCommon(); + switch (databaseIdType) { case ElisaUtils::Track: @@ -565,6 +679,10 @@ if (triggerPlay == ElisaUtils::TriggerPlay) { Q_EMIT ensurePlay(); } + + if (enqueueMode == ElisaUtils::ReplacePlayList) { + d->mForceUndo = false; + } } bool MediaPlayList::savePlaylist(const QUrl &fileName) @@ -598,6 +716,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)); } @@ -655,7 +774,7 @@ for (auto &oneData : persistentState) { auto trackData = oneData.toStringList(); - if (trackData.size() != 5) { + if (trackData.size() != 6) { continue; } @@ -665,7 +784,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 +838,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(); @@ -875,6 +998,17 @@ Q_EMIT repeatPlayChanged(); } +void MediaPlayList::displayOrHideUndoInline(bool value) +{ + if(value){ + Q_EMIT displayUndoInline(); + }else { + if(!d->mForceUndo){ + Q_EMIT hideUndoInline(); + } + } +} + void MediaPlayList::skipNextTrack() { if (!d->mCurrentTrack.isValid()) { 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 { @@ -210,7 +211,6 @@ } Item { Layout.fillHeight: true } - } PlayListBasicView { @@ -230,6 +230,37 @@ onDisplayError: topItem.displayError(errorText) } + + Kirigami.InlineMessage { + + Connections { + target: elisa.mediaPlayList + onDisplayUndoInline: undoClear.visible = true + } + + Connections { + target: elisa.mediaPlayList + onHideUndoInline: undoClear.visible = false + } + + id: undoClear + + 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() + } + ] + } } }