diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index a917272a..e85fc92f 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -1,484 +1,498 @@ include_directories(${elisa_BINARY_DIR}) set(elisaLib_SOURCES mediaplaylist.cpp musicaudiotrack.cpp progressindicator.cpp databaseinterface.cpp datatypes.cpp musiclistenersmanager.cpp managemediaplayercontrol.cpp manageheaderbar.cpp manageaudioplayer.cpp trackslistener.cpp elisaapplication.cpp modeldataloader.cpp elisautils.cpp abstractfile/abstractfilelistener.cpp abstractfile/abstractfilelisting.cpp filescanner.cpp viewmanager.cpp powermanagementinterface.cpp file/filelistener.cpp file/localfilelisting.cpp models/datamodel.cpp models/abstractmediaproxymodel.cpp models/gridviewproxymodel.cpp models/alltracksproxymodel.cpp models/singlealbumproxymodel.cpp models/trackmetadatamodel.cpp models/trackcontextmetadatamodel.cpp models/viewsmodel.cpp ) ecm_qt_declare_logging_category(elisaLib_SOURCES HEADER "indexersManager.h" IDENTIFIER "orgKdeElisaIndexersManager" CATEGORY_NAME "org.kde.elisa.indexers.manager" DEFAULT_SEVERITY Info ) ecm_qt_declare_logging_category(elisaLib_SOURCES HEADER "databaseLogging.h" IDENTIFIER "orgKdeElisaDatabase" CATEGORY_NAME "org.kde.elisa.database" DEFAULT_SEVERITY Info ) ecm_qt_declare_logging_category(elisaLib_SOURCES HEADER "abstractfile/indexercommon.h" IDENTIFIER "orgKdeElisaIndexer" CATEGORY_NAME "org.kde.elisa.indexer" DEFAULT_SEVERITY Info ) +ecm_qt_declare_logging_category(elisaLib_SOURCES + HEADER "models/modelLogging.h" + IDENTIFIER "orgKdeElisaModel" + CATEGORY_NAME "org.kde.elisa.model" + DEFAULT_SEVERITY Info + ) + +ecm_qt_declare_logging_category(elisaLib_SOURCES + HEADER "playListLogging.h" + IDENTIFIER "orgKdeElisaPlayList" + CATEGORY_NAME "org.kde.elisa.playlist" + DEFAULT_SEVERITY Info + ) + if (LIBVLC_FOUND) ecm_qt_declare_logging_category(elisaLib_SOURCES HEADER "vlcLogging.h" IDENTIFIER "orgKdeElisaPlayerVlc" CATEGORY_NAME "org.kde.elisa.player.vlc" DEFAULT_SEVERITY Info ) set(elisaLib_SOURCES ${elisaLib_SOURCES} audiowrapper_libvlc.cpp ) else() ecm_qt_declare_logging_category(elisaLib_SOURCES HEADER "qtMultimediaLogging.h" IDENTIFIER "orgKdeElisaPlayerQtMultimedia" CATEGORY_NAME "org.kde.elisa.player.qtMultimedia" DEFAULT_SEVERITY Info ) set(elisaLib_SOURCES ${elisaLib_SOURCES} audiowrapper_qtmultimedia.cpp ) endif() if (ANDROID) set(elisaLib_SOURCES ${elisaLib_SOURCES} android/androidmusiclistener.cpp ) endif() if (KF5KIO_FOUND) set(elisaLib_SOURCES ${elisaLib_SOURCES} models/filebrowsermodel.cpp models/filebrowserproxymodel.cpp ) endif() if (KF5Baloo_FOUND) if (Qt5DBus_FOUND) ecm_qt_declare_logging_category(elisaLib_SOURCES HEADER "baloo/baloocommon.h" IDENTIFIER "orgKdeElisaBaloo" CATEGORY_NAME "org.kde.elisa.baloo" DEFAULT_SEVERITY Info ) set(elisaLib_SOURCES ${elisaLib_SOURCES} baloo/localbaloofilelisting.cpp baloo/baloolistener.cpp baloo/baloodetector.cpp ) qt5_add_dbus_interface(elisaLib_SOURCES ${BALOO_DBUS_INTERFACES_DIR}/org.kde.baloo.main.xml baloo/main) qt5_add_dbus_interface(elisaLib_SOURCES ${BALOO_DBUS_INTERFACES_DIR}/org.kde.baloo.fileindexer.xml baloo/fileindexer) qt5_add_dbus_interface(elisaLib_SOURCES ${BALOO_DBUS_INTERFACES_DIR}/org.kde.baloo.scheduler.xml baloo/scheduler) qt5_add_dbus_adaptor(elisaLib_SOURCES ${BALOO_DBUS_INTERFACES_DIR}/org.kde.BalooWatcherApplication.xml baloo/localbaloofilelisting.h LocalBalooFileListing) endif() endif() if (Qt5DBus_FOUND) set(elisaLib_SOURCES ${elisaLib_SOURCES} mpris2/mpris2.cpp mpris2/mediaplayer2.cpp mpris2/mediaplayer2player.cpp ) endif() if (UPNPQT_FOUND) set(elisaLib_SOURCES ${elisaLib_SOURCES} upnp/upnpcontrolcontentdirectory.cpp upnp/upnpcontentdirectorymodel.cpp upnp/upnpcontrolconnectionmanager.cpp upnp/upnpcontrolmediaserver.cpp upnp/didlparser.cpp upnp/upnplistener.cpp upnp/upnpdiscoverallmusic.cpp ) endif() if (KF5Baloo_FOUND) if (Qt5DBus_FOUND) qt5_add_dbus_interface(elisaLib_SOURCES ${BALOO_DBUS_INTERFACES_DIR}/org.kde.baloo.fileindexer.xml baloo/fileindexer) qt5_add_dbus_interface(elisaLib_SOURCES ${BALOO_DBUS_INTERFACES_DIR}/org.kde.baloo.scheduler.xml baloo/scheduler) set(elisaLib_SOURCES ${elisaLib_SOURCES} ../src/baloo/baloolistener.cpp ../src/baloo/localbaloofilelisting.cpp ) endif() endif() kconfig_add_kcfg_files(elisaLib_SOURCES ../src/elisa_settings.kcfgc ) set(elisaLib_SOURCES ${elisaLib_SOURCES} ../src/elisa_core.kcfg ) add_library(elisaLib ${elisaLib_SOURCES}) target_link_libraries(elisaLib LINK_PUBLIC Qt5::Multimedia LINK_PRIVATE Qt5::Core Qt5::Sql Qt5::Widgets Qt5::Concurrent Qt5::Qml KF5::I18n KF5::CoreAddons KF5::ConfigCore KF5::ConfigGui) if (KF5FileMetaData_FOUND) target_link_libraries(elisaLib LINK_PRIVATE KF5::FileMetaData ) endif() if (KF5KIO_FOUND) target_link_libraries(elisaLib LINK_PUBLIC KF5::KIOCore KF5::KIOFileWidgets KF5::KIOWidgets ) endif() if (KF5XmlGui_FOUND) target_link_libraries(elisaLib LINK_PUBLIC KF5::XmlGui ) endif() if (KF5ConfigWidgets_FOUND) target_link_libraries(elisaLib LINK_PUBLIC KF5::ConfigWidgets ) endif() if (KF5KCMUtils_FOUND) target_link_libraries(elisaLib LINK_PUBLIC KF5::KCMUtils ) endif() if (KF5Baloo_FOUND) if (Qt5DBus_FOUND) target_link_libraries(elisaLib LINK_PUBLIC KF5::Baloo ) endif() endif() if (Qt5DBus_FOUND) target_link_libraries(elisaLib LINK_PUBLIC Qt5::DBus ) if (KF5DBusAddons_FOUND) target_link_libraries(elisaLib LINK_PUBLIC KF5::DBusAddons ) endif() endif() if (LIBVLC_FOUND) target_include_directories(elisaLib PRIVATE ${LIBVLC_INCLUDE_DIR} ) target_link_libraries(elisaLib LINK_PRIVATE ${LIBVLC_LIBRARY} ) endif() if (ANDROID) target_link_libraries(elisaLib LINK_PUBLIC Qt5::AndroidExtras ) endif() generate_export_header(elisaLib BASE_NAME ElisaLib EXPORT_FILE_NAME elisaLib_export.h) set_target_properties(elisaLib PROPERTIES VERSION 0.1 SOVERSION 0 EXPORT_NAME ElisaLib ) if (NOT APPLE AND NOT WIN32) install(TARGETS elisaLib LIBRARY DESTINATION ${KDE_INSTALL_FULL_LIBDIR}/elisa NAMELINK_SKIP RUNTIME DESTINATION ${KDE_INSTALL_FULL_LIBDIR}/elisa BUNDLE DESTINATION ${KDE_INSTALL_FULL_LIBDIR}/elisa ) else() install(TARGETS elisaLib ${INSTALL_TARGETS_DEFAULT_ARGS}) endif() set(elisaqmlplugin_SOURCES elisaqmlplugin.cpp elisautils.cpp ) if (KF5FileMetaData_FOUND) set(elisaqmlplugin_SOURCES ${elisaqmlplugin_SOURCES} embeddedcoverageimageprovider.cpp ) endif() add_library(elisaqmlplugin SHARED ${elisaqmlplugin_SOURCES}) target_link_libraries(elisaqmlplugin LINK_PRIVATE Qt5::Quick Qt5::Widgets KF5::ConfigCore KF5::ConfigGui elisaLib ) if (KF5FileMetaData_FOUND) target_link_libraries(elisaqmlplugin LINK_PRIVATE KF5::FileMetaData ) endif() set_target_properties(elisaqmlplugin PROPERTIES LIBRARY_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/bin/org/kde/elisa ) if (NOT APPLE AND NOT WIN32) set_target_properties(elisaqmlplugin PROPERTIES INSTALL_RPATH "${KDE_INSTALL_FULL_LIBDIR}/elisa;${CMAKE_INSTALL_RPATH}" ) endif() install(TARGETS elisaqmlplugin DESTINATION ${QML_INSTALL_DIR}/org/kde/elisa/) install(FILES qmldir DESTINATION ${QML_INSTALL_DIR}/org/kde/elisa) add_custom_target(copy) add_custom_target(copy2) file(MAKE_DIRECTORY ${CMAKE_BINARY_DIR}/bin/org/kde/elisa) add_custom_command(TARGET copy PRE_BUILD COMMAND ${CMAKE_COMMAND} -E copy ${CMAKE_CURRENT_SOURCE_DIR}/qmldir ${CMAKE_BINARY_DIR}/bin/org/kde/elisa/) add_custom_command(TARGET copy2 PRE_BUILD COMMAND ${CMAKE_COMMAND} -E copy ${CMAKE_CURRENT_SOURCE_DIR}/plugins.qmltypes ${CMAKE_BINARY_DIR}/bin/org/kde/elisa/) add_dependencies(elisaqmlplugin copy copy2) if (Qt5Quick_FOUND AND Qt5Widgets_FOUND) set(elisa_SOURCES main.cpp windows/WindowsTheme.qml windows/PlatformIntegration.qml android/ElisaMainWindow.qml android/AndroidTheme.qml android/PlatformIntegration.qml android/AlbumsView.qml android/ArtistsView.qml android/TracksView.qml android/GenresView.qml qml/ElisaMainWindow.qml qml/ApplicationMenu.qml qml/NativeApplicationMenu.qml qml/BaseTheme.qml qml/Theme.qml qml/PlatformIntegration.qml qml/LabelWithToolTip.qml qml/RatingStar.qml qml/DraggableItem.qml qml/TrackImportNotification.qml qml/HeaderBar.qml qml/NavigationActionBar.qml qml/MediaPlayerControl.qml qml/ContextView.qml qml/ContentView.qml qml/ViewSelector.qml qml/ViewSelectorDelegate.qml qml/DataGridView.qml qml/DataListView.qml qml/MediaPlayListView.qml qml/PlayListBasicView.qml qml/PlayListEntry.qml qml/SimplePlayListView.qml qml/PlayListAlbumHeader.qml qml/BasicPlayListAlbumHeader.qml qml/MetaDataDelegate.qml qml/EditableMetaDataDelegate.qml qml/TracksDiscHeader.qml qml/MediaTrackMetadataView.qml qml/GridBrowserView.qml qml/GridBrowserDelegate.qml qml/ListBrowserView.qml qml/ListBrowserDelegate.qml qml/FileBrowserView.qml qml/ScrollHelper.qml qml/FlatButtonWithToolTip.qml qml/HeaderFooterToolbar.qml ) qt5_add_resources(elisa_SOURCES resources.qrc) set_property(SOURCE qrc_resources.cpp PROPERTY SKIP_AUTOMOC ON) set(elisa_ICONS_PNG ../icons/128-apps-elisa.png ../icons/64-apps-elisa.png ../icons/48-apps-elisa.png ../icons/32-apps-elisa.png ../icons/22-apps-elisa.png ../icons/16-apps-elisa.png ) # add icons to application sources, to have them bundled ecm_add_app_icon(elisa_SOURCES ICONS ${elisa_ICONS_PNG}) add_executable(elisa ${elisa_SOURCES}) target_include_directories(elisa PRIVATE ${KDSoap_INCLUDE_DIRS}) target_link_libraries(elisa LINK_PRIVATE elisaLib Qt5::Widgets Qt5::QuickControls2 KF5::I18n KF5::CoreAddons KF5::ConfigCore KF5::ConfigGui ) if (ANDROID) target_link_libraries(elisa LINK_PRIVATE Qt5::AndroidExtras Qt5::Svg Qt5::Sql Qt5::Concurrent KF5::Kirigami2 ) endif() if (KF5Crash_FOUND) target_link_libraries(elisa LINK_PRIVATE KF5::Crash ) endif() if (KF5Declarative_FOUND) target_link_libraries(elisa LINK_PRIVATE KF5::Declarative ) endif() if (NOT APPLE AND NOT WIN32) set_target_properties(elisa PROPERTIES INSTALL_RPATH "${KDE_INSTALL_FULL_LIBDIR}/elisa;${CMAKE_INSTALL_RPATH}" ) endif() install(TARGETS elisa ${INSTALL_TARGETS_DEFAULT_ARGS}) endif() if (KF5ConfigWidgets_FOUND AND KF5Declarative_FOUND) add_subdirectory(localFileConfiguration) endif() set(elisaImport_SOURCES elisaimport.cpp elisaimportapplication.cpp ) kconfig_add_kcfg_files(elisaImport_SOURCES ../src/elisa_settings.kcfgc ) set(elisaImport_SOURCES ${elisaImport_SOURCES} ../src/elisa_core.kcfg ) add_executable(elisaImport ${elisaImport_SOURCES}) target_link_libraries(elisaImport LINK_PRIVATE KF5::ConfigCore KF5::ConfigGui elisaLib ) if (KF5FileMetaData_FOUND) target_link_libraries(elisaImport LINK_PRIVATE KF5::FileMetaData ) endif() set(QML_IMPORT_PATH ${CMAKE_BINARY_DIR}/bin CACHE INTERNAL "qml import path" FORCE) if (ANDROID) kirigami_package_breeze_icons(ICONS elisa) endif() diff --git a/src/mediaplaylist.cpp b/src/mediaplaylist.cpp index 95c12106..b7c67a39 100644 --- a/src/mediaplaylist.cpp +++ b/src/mediaplaylist.cpp @@ -1,1388 +1,1396 @@ /* * Copyright 2015-2017 Matthieu Gallien * * This program is free software: you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 3 of the License, or (at your option) any later version. * * This program 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 * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with this program. If not, see . */ #include "mediaplaylist.h" + +#include "playListLogging.h" #include "datatypes.h" #include "musicaudiotrack.h" #include "musiclistenersmanager.h" #include #include #include #include #include #include #include #include #include #include class MediaPlayListPrivate { public: QList mData; QList mTrackData; MusicListenersManager* mMusicListenersManager = nullptr; QPersistentModelIndex mPreviousTrack; QPersistentModelIndex mCurrentTrack; QPersistentModelIndex mNextTrack; QVariantMap mPersistentState; QMediaPlaylist mLoadPlaylist; int mCurrentPlayListPosition = 0; bool mRandomPlay = false; bool mRepeatPlay = false; bool mForceUndo = false; QList mRandomPositions = {0, 0, 0}; }; 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); } 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::DatabaseIdRole)] = "databaseId"; roles[static_cast(ColumnsRoles::TitleRole)] = "title"; roles[static_cast(ColumnsRoles::StringDurationRole)] = "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::CountRole)] = "count"; roles[static_cast(ColumnsRoles::IsPlayingRole)] = "isPlaying"; roles[static_cast(ColumnsRoles::IsSingleDiscAlbumRole)] = "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"; roles[static_cast(ColumnsRoles::AlbumIdRole)] = "albumId"; roles[static_cast(ColumnsRoles::AlbumSectionRole)] = "albumSection"; roles[static_cast(ColumnsRoles::ElementTypeRole)] = "entryType"; return roles; } QVariant MediaPlayList::data(const QModelIndex &index, int role) const { auto result = QVariant(); if (!index.isValid()) { return result; } if (d->mData[index.row()].mIsValid) { switch(role) { case ColumnsRoles::IsValidRole: result = d->mData[index.row()].mIsValid; break; case ColumnsRoles::IsPlayingRole: result = d->mData[index.row()].mIsPlaying; break; case ColumnsRoles::StringDurationRole: { QTime trackDuration = d->mTrackData[index.row()][TrackDataType::key_type::DurationRole].toTime(); if (trackDuration.hour() == 0) { result = trackDuration.toString(QStringLiteral("mm:ss")); } else { result = trackDuration.toString(); } break; } case ColumnsRoles::AlbumSectionRole: result = QJsonDocument{QJsonArray{d->mTrackData[index.row()][TrackDataType::key_type::AlbumRole].toString(), d->mTrackData[index.row()][TrackDataType::key_type::AlbumArtistRole].toString(), d->mTrackData[index.row()][TrackDataType::key_type::ImageUrlRole].toUrl().toString()}}.toJson(); break; default: const auto &trackData = d->mTrackData[index.row()]; auto roleEnum = static_cast(role); auto itData = trackData.find(roleEnum); if (itData != trackData.end()) { result = itData.value(); } else { result = {}; } } } else { switch(role) { case ColumnsRoles::IsValidRole: result = d->mData[index.row()].mIsValid; break; case ColumnsRoles::TitleRole: result = d->mData[index.row()].mTitle; 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::IsSingleDiscAlbumRole: result = false; break; case Qt::DisplayRole: result = d->mData[index.row()].mTitle; break; case ColumnsRoles::ImageUrlRole: result = QUrl(QStringLiteral("image://icon/error")); break; case ColumnsRoles::ShadowForImageRole: result = false; break; case ColumnsRoles::AlbumSectionRole: result = QJsonDocument{QJsonArray{d->mData[index.row()].mAlbum.toString(), d->mData[index.row()].mArtist.toString(), QUrl(QStringLiteral("image://icon/error")).toString()}}.toJson(); break; default: result = {}; } } 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::IsPlayingRole) { 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; } case ColumnsRoles::TitleRole: { modelModified = true; d->mData[index.row()].mTitle = value; d->mTrackData[index.row()][static_cast(role)] = value; Q_EMIT dataChanged(index, index, {role}); if (!d->mCurrentTrack.isValid()) { resetCurrentTrack(); } break; } case ColumnsRoles::ArtistRole: { modelModified = true; d->mData[index.row()].mArtist = value; d->mTrackData[index.row()][static_cast(role)] = value; 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); 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->mNextTrack.isValid() || !d->mPreviousTrack.isValid()) { notifyPreviousAndNextTracks(); } if (!d->mCurrentTrack.isValid() && rowCount(parent) <= row) { resetCurrentTrack(); } if (d->mRandomPlay) { createRandomList(); } Q_EMIT tracksCountChanged(); Q_EMIT remainingTracksChanged(); 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; } 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(); 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::enqueueRestoredEntry(const MediaPlayListEntry &newEntry) { enqueueCommon(); beginInsertRows(QModelIndex(), d->mData.size(), d->mData.size()); d->mData.push_back(newEntry); d->mTrackData.push_back({}); endInsertRows(); restorePlayListPosition(); if (!d->mCurrentTrack.isValid()) { resetCurrentTrack(); } Q_EMIT tracksCountChanged(); Q_EMIT remainingTracksChanged(); Q_EMIT persistentStateChanged(); if (!newEntry.mIsValid) { if (newEntry.mEntryType == ElisaUtils::Radio) { Q_EMIT newEntryInList(newEntry.mId, {}, ElisaUtils::Radio); } else if (newEntry.mTrackUrl.isValid()) { auto entryURL = newEntry.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, ElisaUtils::FileName); } } else { Q_EMIT newTrackByNameInList(newEntry.mTitle, newEntry.mArtist, newEntry.mAlbum, newEntry.mTrackNumber, newEntry.mDiscNumber); } } else { Q_EMIT newEntryInList(newEntry.mId, {}, ElisaUtils::Track); } if (!newEntry.mIsValid) { Q_EMIT dataChanged(index(rowCount() - 1, 0), index(rowCount() - 1, 0), {MediaPlayList::IsPlayingRole}); if (!d->mCurrentTrack.isValid()) { resetCurrentTrack(); } } } void MediaPlayList::enqueueArtist(const QString &artistName) { + qCDebug(orgKdeElisaPlayList()) << "MediaPlayList::enqueueArtist" << artistName; enqueueCommon(); auto newEntry = MediaPlayListEntry{artistName}; newEntry.mEntryType = ElisaUtils::Artist; beginInsertRows(QModelIndex(), d->mData.size(), d->mData.size()); d->mData.push_back(newEntry); d->mTrackData.push_back({}); endInsertRows(); restorePlayListPosition(); if (!d->mCurrentTrack.isValid()) { resetCurrentTrack(); } Q_EMIT tracksCountChanged(); Q_EMIT remainingTracksChanged(); Q_EMIT newEntryInList(0, artistName, newEntry.mEntryType); Q_EMIT persistentStateChanged(); } void MediaPlayList::enqueueFilesList(const ElisaUtils::EntryDataList &newEntries) { + qCDebug(orgKdeElisaPlayList()) << "MediaPlayList::enqueueFilesList"; 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()) { auto entryURL = newEntry.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, newEntry.mEntryType); } } } endInsertRows(); restorePlayListPosition(); if (!d->mCurrentTrack.isValid()) { resetCurrentTrack(); } Q_EMIT tracksCountChanged(); Q_EMIT remainingTracksChanged(); Q_EMIT persistentStateChanged(); Q_EMIT dataChanged(index(rowCount() - 1, 0), index(rowCount() - 1, 0), {MediaPlayList::IsPlayingRole}); } void MediaPlayList::enqueueTracksListById(const ElisaUtils::EntryDataList &newEntries, ElisaUtils::PlayListEntryType type) { + qCDebug(orgKdeElisaPlayList()) << "MediaPlayList::enqueueTracksListById" << newEntries.size() << type; enqueueCommon(); beginInsertRows(QModelIndex(), d->mData.size(), d->mData.size() + newEntries.size() - 1); for (const auto &newTrack : newEntries) { auto newMediaPlayListEntry = MediaPlayListEntry{std::get<0>(newTrack), std::get<1>(newTrack), type}; d->mData.push_back(newMediaPlayListEntry); d->mTrackData.push_back({}); Q_EMIT newEntryInList(newMediaPlayListEntry.mId, newMediaPlayListEntry.mTitle.toString(), newMediaPlayListEntry.mEntryType); } endInsertRows(); restorePlayListPosition(); if (!d->mCurrentTrack.isValid()) { resetCurrentTrack(); } Q_EMIT tracksCountChanged(); Q_EMIT remainingTracksChanged(); Q_EMIT persistentStateChanged(); Q_EMIT dataChanged(index(rowCount() - 1, 0), index(rowCount() - 1, 0), {MediaPlayList::IsPlayingRole}); } void MediaPlayList::enqueueOneEntry(const ElisaUtils::EntryData &entryData, ElisaUtils::PlayListEntryType type) { + qCDebug(orgKdeElisaPlayList()) << "MediaPlayList::enqueueOneEntry" << std::get<0>(entryData) << std::get<1>(entryData) << 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({}); Q_EMIT newEntryInList(std::get<0>(entryData), std::get<1>(entryData), type); endInsertRows(); Q_EMIT tracksCountChanged(); Q_EMIT remainingTracksChanged(); Q_EMIT persistentStateChanged(); } void MediaPlayList::enqueueMultipleEntries(const ElisaUtils::EntryDataList &entriesData, ElisaUtils::PlayListEntryType type) { + qCDebug(orgKdeElisaPlayList()) << "MediaPlayList::enqueueMultipleEntries" << entriesData.size() << 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}); d->mTrackData.push_back({}); Q_EMIT newEntryInList(std::get<0>(entryData), std::get<1>(entryData), type); } endInsertRows(); Q_EMIT tracksCountChanged(); Q_EMIT remainingTracksChanged(); Q_EMIT persistentStateChanged(); } void MediaPlayList::replaceAndPlay(const ElisaUtils::EntryData &newEntry, ElisaUtils::PlayListEntryType databaseIdType) { + qCDebug(orgKdeElisaPlayList()) << "MediaPlayList::replaceAndPlay" << std::get<0>(newEntry) << std::get<1>(newEntry) << databaseIdType; enqueue(newEntry, databaseIdType, ElisaUtils::PlayListEnqueueMode::ReplacePlayList, ElisaUtils::PlayListEnqueueTriggerPlay::TriggerPlay); } 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(); endRemoveRows(); d->mCurrentPlayListPosition = 0; d->mCurrentTrack = QPersistentModelIndex{}; notifyCurrentTrackChanged(); displayOrHideUndoInline(true); Q_EMIT tracksCountChanged(); Q_EMIT remainingTracksChanged(); 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 remainingTracksChanged(); Q_EMIT persistentStateChanged(); Q_EMIT dataChanged(index(rowCount() - 1, 0), index(rowCount() - 1, 0), {MediaPlayList::IsPlayingRole}); displayOrHideUndoInline(false); Q_EMIT undoClearPlayListPlayer(); } void MediaPlayList::updateRadioData(const QVariant &value, int role) { auto convertedRole = static_cast(role); if (d->mCurrentTrack.data(convertedRole) != value) { this->setData(d->mCurrentTrack, value, role); } } 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 QString &localFileName) { d->mLoadPlaylist.clear(); d->mLoadPlaylist.load(QUrl::fromLocalFile(localFileName), "m3u"); } void MediaPlayList::loadPlaylist(const QUrl &fileName) { d->mLoadPlaylist.clear(); d->mLoadPlaylist.load(fileName, "m3u"); } void MediaPlayList::enqueue(const ElisaUtils::EntryData &newEntry, ElisaUtils::PlayListEntryType databaseIdType) { enqueue(newEntry, databaseIdType, ElisaUtils::PlayListEnqueueMode::AppendPlayList, ElisaUtils::PlayListEnqueueTriggerPlay::DoNotTriggerPlay); } void MediaPlayList::enqueue(const ElisaUtils::EntryDataList &newEntries, ElisaUtils::PlayListEntryType databaseIdType) { enqueue(newEntries, databaseIdType, ElisaUtils::PlayListEnqueueMode::AppendPlayList, ElisaUtils::PlayListEnqueueTriggerPlay::DoNotTriggerPlay); } void MediaPlayList::enqueue(qulonglong newEntryDatabaseId, const QString &newEntryTitle, ElisaUtils::PlayListEntryType databaseIdType, ElisaUtils::PlayListEnqueueMode enqueueMode, ElisaUtils::PlayListEnqueueTriggerPlay triggerPlay) { enqueue(ElisaUtils::EntryData{newEntryDatabaseId, newEntryTitle}, databaseIdType, enqueueMode, triggerPlay); } void MediaPlayList::enqueue(const ElisaUtils::EntryData &newEntry, ElisaUtils::PlayListEntryType databaseIdType, 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: case ElisaUtils::Artist: case ElisaUtils::Genre: case ElisaUtils::Track: case ElisaUtils::Radio: enqueueOneEntry(newEntry, databaseIdType); break; case ElisaUtils::FileName: enqueueFilesList({newEntry}); break; case ElisaUtils::Lyricist: case ElisaUtils::Composer: case ElisaUtils::Unknown: break; } if (triggerPlay == ElisaUtils::TriggerPlay) { Q_EMIT ensurePlay(); } if (enqueueMode == ElisaUtils::ReplacePlayList) { d->mForceUndo = false; } } void MediaPlayList::enqueue(const ElisaUtils::EntryDataList &newEntries, ElisaUtils::PlayListEntryType databaseIdType, ElisaUtils::PlayListEnqueueMode enqueueMode, ElisaUtils::PlayListEnqueueTriggerPlay triggerPlay) { if (newEntries.isEmpty()) { return; } if (enqueueMode == ElisaUtils::ReplacePlayList) { if(d->mData.size()>0){ d->mForceUndo = true; } clearPlayList(); } enqueueCommon(); switch (databaseIdType) { case ElisaUtils::Track: case ElisaUtils::Radio: enqueueTracksListById(newEntries, databaseIdType); break; case ElisaUtils::FileName: enqueueFilesList(newEntries); break; case ElisaUtils::Album: case ElisaUtils::Artist: case ElisaUtils::Genre: enqueueMultipleEntries(newEntries, databaseIdType); break; case ElisaUtils::Lyricist: case ElisaUtils::Composer: case ElisaUtils::Unknown: break; } if (triggerPlay == ElisaUtils::TriggerPlay) { Q_EMIT ensurePlay(); } if (enqueueMode == ElisaUtils::ReplacePlayList) { d->mForceUndo = false; } } 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(QString::number(oneTrack.databaseId())); oneData.push_back(oneTrack.title()); oneData.push_back(oneTrack.artist()); if (oneTrack.hasAlbum()) { oneData.push_back(oneTrack.album()); } else { oneData.push_back({}); } if (oneTrack.hasTrackNumber()) { oneData.push_back(QString::number(oneTrack.trackNumber())); } else { oneData.push_back({}); } if (oneTrack.hasDiscNumber()) { oneData.push_back(QString::number(oneTrack.discNumber())); } else { oneData.push_back({}); } oneData.push_back(QString::number(oneEntry.mEntryType)); 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::previousTrack() const { return d->mPreviousTrack; } QPersistentModelIndex MediaPlayList::currentTrack() const { return d->mCurrentTrack; } QPersistentModelIndex MediaPlayList::nextTrack() const { return d->mNextTrack; } 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() != 7) { continue; } auto restoredId = trackData[0].toULongLong(); auto restoredTitle = trackData[1]; auto restoredArtist = trackData[2]; auto restoredAlbum = trackData[3]; auto restoredTrackNumber = trackData[4]; auto restoredDiscNumber = trackData[5]; auto mEntryType = static_cast(trackData[6].toInt()); enqueueRestoredEntry({restoredId, restoredTitle, restoredArtist, restoredAlbum, restoredTrackNumber, restoredDiscNumber, mEntryType}); } 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); } if (d->mRandomPlay) { createRandomList(); } } void MediaPlayList::tracksListAdded(qulonglong newDatabaseId, const QString &entryTitle, ElisaUtils::PlayListEntryType databaseIdType, const ListTrackDataType &tracks) { for (int playListIndex = 0; playListIndex < d->mData.size(); ++playListIndex) { auto &oneEntry = d->mData[playListIndex]; if (oneEntry.mEntryType != databaseIdType) { continue; } if (oneEntry.mTitle != entryTitle) { continue; } if (newDatabaseId != 0 && oneEntry.mId != newDatabaseId) { continue; } d->mTrackData[playListIndex] = tracks.first(); oneEntry.mId = tracks.first().databaseId(); oneEntry.mIsValid = true; oneEntry.mEntryType = ElisaUtils::Track; 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) { auto newEntry = MediaPlayListEntry{tracks[trackIndex]}; newEntry.mEntryType = ElisaUtils::Track; d->mData.insert(playListIndex + trackIndex, newEntry); d->mTrackData.insert(playListIndex + trackIndex, tracks[trackIndex]); } endInsertRows(); restorePlayListPosition(); if (!d->mCurrentTrack.isValid()) { resetCurrentTrack(); } Q_EMIT tracksCountChanged(); Q_EMIT remainingTracksChanged(); } Q_EMIT persistentStateChanged(); } } void MediaPlayList::trackChanged(const TrackDataType &track) { for (int i = 0; i < d->mData.size(); ++i) { auto &oneEntry = d->mData[i]; if (oneEntry.mEntryType != ElisaUtils::Artist && oneEntry.mIsValid) { if (oneEntry.mTrackUrl.toUrl().isValid() && track.resourceURI() != oneEntry.mTrackUrl.toUrl()) { continue; } if (!oneEntry.mTrackUrl.toUrl().isValid() && (oneEntry.mId == 0 || track.databaseId() != oneEntry.mId)) { continue; } const auto &trackData = d->mTrackData[i]; if (!trackData.empty()) { bool sameData = true; for (auto oneKeyIterator = track.constKeyValueBegin(); oneKeyIterator != track.constKeyValueEnd(); ++oneKeyIterator) { if (trackData[(*oneKeyIterator).first] != (*oneKeyIterator).second) { sameData = false; break; } } if (sameData) { continue; } } d->mTrackData[i] = track; Q_EMIT dataChanged(index(i, 0), index(i, 0), {}); restorePlayListPosition(); if (!d->mCurrentTrack.isValid()) { resetCurrentTrack(); } continue; } else if (oneEntry.mEntryType == ElisaUtils::Radio ) { if (track.databaseId() != oneEntry.mId) { 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; } else if (oneEntry.mEntryType != ElisaUtils::Artist && !oneEntry.mIsValid && !oneEntry.mTrackUrl.isValid()) { if (track.find(TrackDataType::key_type::TitleRole) != track.end() && track.title() != oneEntry.mTitle) { continue; } if (track.find(TrackDataType::key_type::AlbumRole) != track.end() && track.album() != oneEntry.mAlbum) { continue; } if (track.find(TrackDataType::key_type::TrackNumberRole) != track.end() && track.trackNumber() != oneEntry.mTrackNumber) { continue; } if (track.find(TrackDataType::key_type::DiscNumberRole) != track.end() && 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), {}); restorePlayListPosition(); if (!d->mCurrentTrack.isValid()) { resetCurrentTrack(); } else if (i == d->mCurrentTrack.row()) { notifyCurrentTrackChanged(); } else if (i == d->mNextTrack.row() || i == d->mPreviousTrack.row()) { notifyPreviousAndNextTracks(); } break; } else if (oneEntry.mEntryType != ElisaUtils::Artist && !oneEntry.mIsValid && oneEntry.mTrackUrl.isValid()) { 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(); } else if (i == d->mCurrentTrack.row()) { notifyCurrentTrackChanged(); } else if (i == d->mNextTrack.row() || i == d->mPreviousTrack.row()) { notifyPreviousAndNextTracks(); } 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].album(); 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) { if (d->mRandomPlay != value) { d->mRandomPlay = value; createRandomList(); Q_EMIT randomPlayChanged(); Q_EMIT remainingTracksChanged(); notifyPreviousAndNextTracks(); } } void MediaPlayList::setRepeatPlay(bool value) { if (d->mRepeatPlay != value) { d->mRepeatPlay = value; Q_EMIT repeatPlayChanged(); Q_EMIT remainingTracksChanged(); notifyPreviousAndNextTracks(); } } void MediaPlayList::displayOrHideUndoInline(bool value) { if(value){ Q_EMIT displayUndoInline(); }else { if(!d->mForceUndo){ Q_EMIT hideUndoInline(); } } } void MediaPlayList::skipNextTrack() { if (!d->mCurrentTrack.isValid()) { return; } if (d->mRandomPlay) { d->mRandomPositions.removeFirst(); d->mCurrentTrack = index(d->mRandomPositions.at(1), 0); d->mRandomPositions.append(QRandomGenerator::global()->bounded(rowCount())); } else { if (d->mCurrentTrack.row() >= rowCount() - 1) { d->mCurrentTrack = index(0, 0); if (!d->mRepeatPlay) { Q_EMIT playListFinished(); } } else { d->mCurrentTrack = index(d->mCurrentTrack.row() + 1, 0); } } notifyCurrentTrackChanged(); } void MediaPlayList::skipPreviousTrack() { if (!d->mCurrentTrack.isValid()) { return; } if (d->mRandomPlay) { d->mRandomPositions.removeLast(); d->mCurrentTrack = index(d->mRandomPositions.at(0), 0); d->mRandomPositions.prepend(QRandomGenerator::global()->bounded(rowCount())); } else { if (d->mCurrentTrack.row() == 0) { if (d->mRepeatPlay) { d->mCurrentTrack = index(rowCount() - 1, 0); } else { return; } } else { d->mCurrentTrack = index(d->mCurrentTrack.row() - 1, 0); } } notifyCurrentTrackChanged(); } void MediaPlayList::switchTo(int row) { if (!d->mCurrentTrack.isValid()) { return; } if (d->mRandomPlay) { d->mCurrentTrack = index(row, 0); d->mRandomPositions.replace(1, row); } else { d->mCurrentTrack = index(row, 0); } notifyCurrentTrackChanged(); } void MediaPlayList::trackInError(const 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}); } } } } void MediaPlayList::loadPlayListLoaded() { clearPlayList(); for (int i = 0; i < d->mLoadPlaylist.mediaCount(); ++i) { enqueue(ElisaUtils::EntryData{0, d->mLoadPlaylist.media(i).canonicalUrl().toLocalFile()}, ElisaUtils::FileName); } 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::notifyPreviousAndNextTracks() { if (!d->mCurrentTrack.isValid()) { d->mPreviousTrack = QPersistentModelIndex(); d->mNextTrack = QPersistentModelIndex(); } auto mOldPreviousTrack = d->mPreviousTrack; auto mOldNextTrack = d->mNextTrack; // use random list for previous and next types if (d->mRandomPlay) { d->mPreviousTrack = index(d->mRandomPositions.first(), 0); d->mNextTrack = index(d->mRandomPositions.last(), 0); } else if (d->mRepeatPlay) { // forward to end or begin when repeating if (d->mCurrentTrack.row() == 0) { d->mPreviousTrack = index(rowCount() - 1, 0); } else { d->mPreviousTrack = index(d->mCurrentTrack.row() - 1, 0); } if (d->mCurrentTrack.row() == rowCount() - 1) { d->mNextTrack = index(0, 0); } else { d->mNextTrack = index(d->mCurrentTrack.row() + 1, 0); } } else { // return nothing if no tracks available if (d->mCurrentTrack.row() == 0) { d->mPreviousTrack = QPersistentModelIndex(); } else { d->mPreviousTrack = index(d->mCurrentTrack.row() - 1, 0); } if (d->mCurrentTrack.row() == rowCount() - 1) { d->mNextTrack = QPersistentModelIndex(); } else { d->mNextTrack = index(d->mCurrentTrack.row() + 1, 0); } } if (d->mPreviousTrack != mOldPreviousTrack) { Q_EMIT previousTrackChanged(d->mPreviousTrack); } if (d->mNextTrack != mOldNextTrack) { Q_EMIT nextTrackChanged(d->mNextTrack); } } void MediaPlayList::notifyCurrentTrackChanged() { // determine previous and next tracks first notifyPreviousAndNextTracks(); Q_EMIT currentTrackChanged(d->mCurrentTrack); Q_EMIT currentTrackRowChanged(); Q_EMIT remainingTracksChanged(); 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()); } } void MediaPlayList::restoreRepeatPlay() { auto repeatPlayStoredValue = d->mPersistentState.find(QStringLiteral("repeatPlay")); if (repeatPlayStoredValue != d->mPersistentState.end()) { setRepeatPlay(repeatPlayStoredValue->toBool()); } } QDebug operator<<(const QDebug &stream, const MediaPlayListEntry &data) { stream << data.mTitle << data.mAlbum << data.mArtist << data.mTrackUrl << data.mTrackNumber << data.mDiscNumber << data.mId << data.mIsValid; return stream; } int MediaPlayList::remainingTracks() const { if (!d->mCurrentTrack.isValid()) { return -1; } if (d->mRandomPlay || d->mRepeatPlay) { return -1; } else { return rowCount() - d->mCurrentTrack.row() - 1; } } void MediaPlayList::createRandomList() { for (auto& position : d->mRandomPositions) { position = QRandomGenerator::global()->bounded(rowCount()); } if (d->mCurrentTrack.isValid()) { d->mRandomPositions.replace(1, d->mCurrentTrack.row()); } } #include "moc_mediaplaylist.cpp" diff --git a/src/trackslistener.cpp b/src/trackslistener.cpp index 2346957d..2a06588e 100644 --- a/src/trackslistener.cpp +++ b/src/trackslistener.cpp @@ -1,273 +1,276 @@ /* * Copyright 2017 Matthieu Gallien * * This program is free software: you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 3 of the License, or (at your option) any later version. * * This program 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 * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with this program. If not, see . */ #include "trackslistener.h" +#include "playListLogging.h" #include "databaseinterface.h" #include "datatypes.h" #include "filescanner.h" #include #include #include #include #include #include class TracksListenerPrivate { public: QSet mTracksByIdSet; QSet mRadiosByIdSet; QList> mTracksByNameSet; QList mTracksByFileNameSet; DatabaseInterface *mDatabase = nullptr; FileScanner mFileScanner; QMimeDatabase mMimeDb; }; TracksListener::TracksListener(DatabaseInterface *database, QObject *parent) : QObject(parent), d(std::make_unique()) { d->mDatabase = database; } TracksListener::~TracksListener() = default; void TracksListener::tracksAdded(const ListTrackDataType &allTracks) { for (const auto &oneTrack : allTracks) { if (d->mTracksByIdSet.contains(oneTrack.databaseId())) { Q_EMIT trackHasChanged(oneTrack); } if (d->mTracksByNameSet.isEmpty()) { return; } for (auto itTrack = d->mTracksByNameSet.begin(); itTrack != d->mTracksByNameSet.end(); ) { if (!std::get<0>(*itTrack).isEmpty() && std::get<0>(*itTrack) != oneTrack.title()) { ++itTrack; continue; } if (!std::get<1>(*itTrack).isEmpty() && std::get<1>(*itTrack) != oneTrack.artist()) { ++itTrack; continue; } if (!std::get<2>(*itTrack).isEmpty() && std::get<2>(*itTrack) != oneTrack.album()) { ++itTrack; continue; } if (std::get<3>(*itTrack) != oneTrack.trackNumber()) { ++itTrack; continue; } if (std::get<4>(*itTrack) != oneTrack.discNumber()) { ++itTrack; continue; } Q_EMIT trackHasChanged(TrackDataType(oneTrack)); d->mTracksByIdSet.insert(oneTrack.databaseId()); itTrack = d->mTracksByNameSet.erase(itTrack); } } } void TracksListener::trackRemoved(qulonglong id) { if (d->mTracksByIdSet.contains(id)) { Q_EMIT trackHasBeenRemoved(id); } } void TracksListener::trackModified(const TrackDataType &modifiedTrack) { if (d->mTracksByIdSet.contains(modifiedTrack.databaseId())) { Q_EMIT trackHasChanged(modifiedTrack); } } void TracksListener::trackByNameInList(const QVariant &title, const QVariant &artist, const QVariant &album, const QVariant &trackNumber, const QVariant &discNumber) { const auto realTitle = title.toString(); const auto realArtist = artist.toString(); const auto albumIsValid = !album.isNull() && album.isValid() && !album.toString().isEmpty(); auto realAlbum = std::optional{}; if (albumIsValid) { realAlbum = album.toString(); } auto trackNumberIsValid = bool{}; const auto trackNumberValue = trackNumber.toInt(&trackNumberIsValid); auto realTrackNumber = std::optional{}; if (trackNumberIsValid) { realTrackNumber = trackNumberValue; } auto discNumberIsValid = bool{}; const auto discNumberValue = discNumber.toInt(&discNumberIsValid); auto realDiscNumber = std::optional{}; if (discNumberIsValid) { realDiscNumber = discNumberValue; } auto newTrackId = d->mDatabase->trackIdFromTitleAlbumTrackDiscNumber(realTitle, realArtist, realAlbum, realTrackNumber, realDiscNumber); if (newTrackId == 0) { auto newTrack = std::tuple(realTitle, realArtist, album.toString(), trackNumber.toInt(), discNumber.toInt()); d->mTracksByNameSet.push_back(newTrack); return; } d->mTracksByIdSet.insert(newTrackId); auto newTrack = d->mDatabase->trackDataFromDatabaseId(newTrackId); if (!newTrack.isEmpty()) { Q_EMIT trackHasChanged(newTrack); } } void TracksListener::trackByFileNameInList(const QUrl &fileName) { auto newTrackId = d->mDatabase->trackIdFromFileName(fileName); if (newTrackId == 0) { auto newTrack = scanOneFile(fileName); if (newTrack.isValid()) { auto oneData = DataTypes::TrackDataType{}; if (!newTrack.title().isEmpty()) { oneData[DataTypes::TrackDataType::key_type::TitleRole] = newTrack.title(); } else { const auto &fileUrl = newTrack.resourceURI(); oneData[DataTypes::TrackDataType::key_type::TitleRole] = fileUrl.fileName(); } oneData[DataTypes::TrackDataType::key_type::ArtistRole] = newTrack.artist(); oneData[DataTypes::TrackDataType::key_type::AlbumRole] = newTrack.albumName(); oneData[DataTypes::TrackDataType::key_type::AlbumIdRole] = newTrack.albumId(); oneData[DataTypes::TrackDataType::key_type::TrackNumberRole] = newTrack.trackNumber(); oneData[DataTypes::TrackDataType::key_type::DiscNumberRole] = newTrack.discNumber(); oneData[DataTypes::TrackDataType::key_type::DurationRole] = newTrack.duration(); oneData[DataTypes::TrackDataType::key_type::MilliSecondsDurationRole] = newTrack.duration().msecsSinceStartOfDay(); oneData[DataTypes::TrackDataType::key_type::ResourceRole] = newTrack.resourceURI(); oneData[DataTypes::TrackDataType::key_type::ImageUrlRole] = newTrack.albumCover(); Q_EMIT trackHasChanged(oneData); return; } d->mTracksByFileNameSet.push_back(fileName); return; } d->mTracksByIdSet.insert(newTrackId); auto newTrack = d->mDatabase->trackDataFromDatabaseId(newTrackId); if (!newTrack.isEmpty()) { Q_EMIT trackHasChanged({newTrack}); } } void TracksListener::newAlbumInList(qulonglong newDatabaseId, const QString &entryTitle) { + qCDebug(orgKdeElisaPlayList()) << "TracksListener::newAlbumInList" << newDatabaseId << entryTitle << d->mDatabase->albumData(newDatabaseId); Q_EMIT tracksListAdded(newDatabaseId, entryTitle, ElisaUtils::Album, d->mDatabase->albumData(newDatabaseId)); } void TracksListener::newEntryInList(qulonglong newDatabaseId, const QString &entryTitle, ElisaUtils::PlayListEntryType databaseIdType) { + qCDebug(orgKdeElisaPlayList()) << "TracksListener::newEntryInList" << newDatabaseId << entryTitle << databaseIdType; switch (databaseIdType) { case ElisaUtils::Track: { d->mTracksByIdSet.insert(newDatabaseId); auto newTrack = d->mDatabase->trackDataFromDatabaseId(newDatabaseId); if (!newTrack.isEmpty()) { Q_EMIT trackHasChanged(newTrack); } break; } case ElisaUtils::Radio: { d->mRadiosByIdSet.insert(newDatabaseId); auto newRadio = d->mDatabase->radioDataFromDatabaseId(newDatabaseId); if (!newRadio.isEmpty()) { Q_EMIT trackHasChanged(newRadio); } break; } case ElisaUtils::Artist: newArtistInList(newDatabaseId, entryTitle); break; case ElisaUtils::FileName: trackByFileNameInList(QUrl::fromLocalFile(entryTitle)); break; case ElisaUtils::Album: newAlbumInList(newDatabaseId, entryTitle); break; case ElisaUtils::Lyricist: case ElisaUtils::Composer: case ElisaUtils::Genre: case ElisaUtils::Unknown: break; } } void TracksListener::newArtistInList(qulonglong newDatabaseId, const QString &artist) { auto newTracks = d->mDatabase->tracksDataFromAuthor(artist); if (newTracks.isEmpty()) { return; } for (const auto &oneTrack : newTracks) { d->mTracksByIdSet.insert(oneTrack.databaseId()); } Q_EMIT tracksListAdded(newDatabaseId, artist, ElisaUtils::Artist, newTracks); } MusicAudioTrack TracksListener::scanOneFile(const QUrl &scanFile) { return d->mFileScanner.scanOneFile(scanFile, d->mMimeDb); } #include "moc_trackslistener.cpp"