From 43aa2ece3ccf618d44dee3925894c25a4842737e Mon Sep 17 00:00:00 2001 From: emeric Date: Wed, 7 Sep 2022 18:35:15 +0200 Subject: [PATCH] Playing a track in the album view will now play the whole album at the selected track index (+ reworked play queue controller) --- src/lms/CMakeLists.txt | 1 + src/lms/ui/LmsApplication.cpp | 11 +- src/lms/ui/PlayQueue.cpp | 60 ++-- src/lms/ui/PlayQueue.hpp | 8 +- src/lms/ui/explore/ArtistView.cpp | 20 +- src/lms/ui/explore/ArtistView.hpp | 13 +- src/lms/ui/explore/Explore.cpp | 290 +++++------------- src/lms/ui/explore/Explore.hpp | 19 +- src/lms/ui/explore/PlayQueueController.cpp | 179 +++++++++++ .../PlayQueueController.hpp} | 41 ++- src/lms/ui/explore/ReleaseView.cpp | 24 +- src/lms/ui/explore/ReleaseView.hpp | 12 +- src/lms/ui/explore/ReleasesView.cpp | 14 +- src/lms/ui/explore/ReleasesView.hpp | 7 +- src/lms/ui/explore/SearchView.cpp | 14 +- src/lms/ui/explore/SearchView.hpp | 9 +- src/lms/ui/explore/TrackListHelpers.cpp | 13 +- src/lms/ui/explore/TrackListHelpers.hpp | 8 +- src/lms/ui/explore/TrackListView.cpp | 20 +- src/lms/ui/explore/TrackListView.hpp | 8 +- src/lms/ui/explore/TrackListsView.hpp | 3 - src/lms/ui/explore/TracksView.cpp | 20 +- src/lms/ui/explore/TracksView.hpp | 7 +- 23 files changed, 434 insertions(+), 367 deletions(-) create mode 100644 src/lms/ui/explore/PlayQueueController.cpp rename src/lms/ui/{PlayQueueAction.hpp => explore/PlayQueueController.hpp} (50%) diff --git a/src/lms/CMakeLists.txt b/src/lms/CMakeLists.txt index b627fe9f..7929bd44 100644 --- a/src/lms/CMakeLists.txt +++ b/src/lms/CMakeLists.txt @@ -32,6 +32,7 @@ add_executable(lms ui/explore/DatabaseCollectorBase.cpp ui/explore/Explore.cpp ui/explore/Filters.cpp + ui/explore/PlayQueueController.cpp ui/explore/ReleaseCollector.cpp ui/explore/ReleaseListHelpers.cpp ui/explore/ReleasesView.cpp diff --git a/src/lms/ui/LmsApplication.cpp b/src/lms/ui/LmsApplication.cpp index ec7c476e..c9992c5c 100644 --- a/src/lms/ui/LmsApplication.cpp +++ b/src/lms/ui/LmsApplication.cpp @@ -502,8 +502,9 @@ LmsApplication::createHome() Wt::WStackedWidget* mainStack {main->bindNew("contents")}; mainStack->setOverflow(Wt::Overflow::Visible); // wt makes it hidden by default - Explore* explore {mainStack->addNew(filters)}; - _playQueue = mainStack->addNew(); + std::unique_ptr playQueue {std::make_unique()}; + Explore* explore {mainStack->addNew(*filters, *playQueue)}; + _playQueue = mainStack->addWidget(std::move(playQueue)); mainStack->addNew(); searchEdit->enterPressed().connect([=] @@ -525,11 +526,7 @@ LmsApplication::createHome() mainStack->addNew(); } - explore->setMaxTrackCountForAction(_playQueue->getCapacity()); - explore->tracksAction.connect([this] (PlayQueueAction action, const std::vector& trackIds) - { - _playQueue->processTracks(action, trackIds); - }); + explore->getPlayQueueController().setMaxTrackCountToEnqueue(_playQueue->getCapacity()); // Events from MediaPlayer _mediaPlayer->playNext.connect([this] diff --git a/src/lms/ui/PlayQueue.cpp b/src/lms/ui/PlayQueue.cpp index a336e443..9731b291 100644 --- a/src/lms/ui/PlayQueue.cpp +++ b/src/lms/ui/PlayQueue.cpp @@ -416,38 +416,46 @@ PlayQueue::enqueueTracks(const std::vector& trackIds) } void -PlayQueue::processTracks(PlayQueueAction action, const std::vector& trackIds) +PlayQueue::play(const std::vector& trackIds) { - std::size_t nbAddedTracks {}; + playAtIndex(trackIds, 0); +} - switch (action) - { - case PlayQueueAction::PlayLast: - nbAddedTracks = enqueueTracks(trackIds); - if (!_trackPos) - loadTrack(0, true); - break; +void +PlayQueue::playShuffled(const std::vector& trackIds) +{ + clearTracks(); + std::vector shuffledTrackIds {trackIds}; + Random::shuffleContainer(shuffledTrackIds); + const std::size_t nbAddedTracks {enqueueTracks(shuffledTrackIds)}; + loadTrack(0, true); - case PlayQueueAction::Play: - clearTracks(); - nbAddedTracks = enqueueTracks(trackIds); - loadTrack(0, true); + notifyAddedTracks(nbAddedTracks); +} - break; +void +PlayQueue::playOrAddLast(const std::vector& trackIds) +{ + const std::size_t nbAddedTracks {enqueueTracks(trackIds)}; + if (!_trackPos) + loadTrack(0, true); - case PlayQueueAction::PlayShuffled: - { - clearTracks(); - { - std::vector shuffledTrackIds {trackIds}; - Random::shuffleContainer(shuffledTrackIds); - nbAddedTracks = enqueueTracks(shuffledTrackIds); - } - loadTrack(0, true); - break; - } - } + notifyAddedTracks(nbAddedTracks); +} +void +PlayQueue::playAtIndex(const std::vector& trackIds, std::size_t index) +{ + clearTracks(); + const std::size_t nbAddedTracks {enqueueTracks(trackIds)}; + loadTrack(index, true); + + notifyAddedTracks(nbAddedTracks); +} + +void +PlayQueue::notifyAddedTracks(std::size_t nbAddedTracks) const +{ if (nbAddedTracks > 0) LmsApp->notifyMsg(Notification::Type::Info, Wt::WString::tr("Lms.PlayQueue.playqueue"), Wt::WString::trn("Lms.PlayQueue.nb-tracks-added", nbAddedTracks).arg(nbAddedTracks), std::chrono::seconds {2}); diff --git a/src/lms/ui/PlayQueue.hpp b/src/lms/ui/PlayQueue.hpp index 2cd67e43..f6e21076 100644 --- a/src/lms/ui/PlayQueue.hpp +++ b/src/lms/ui/PlayQueue.hpp @@ -27,8 +27,8 @@ #include #include "services/database/Object.hpp" +#include "services/database/TrackId.hpp" #include "services/database/TrackListId.hpp" -#include "PlayQueueAction.hpp" #include "common/Template.hpp" @@ -53,7 +53,10 @@ class PlayQueue : public Template public: PlayQueue(); - void processTracks(PlayQueueAction action, const std::vector& trackIds); + void play(const std::vector& trackIds); + void playShuffled(const std::vector& trackIds); + void playOrAddLast(const std::vector& trackIds); // play if queue empty, otherwise just add last + void playAtIndex(const std::vector& trackIds, std::size_t index); // play the next track in the queue void playNext(); @@ -70,6 +73,7 @@ class PlayQueue : public Template constexpr std::size_t getCapacity() const { return _capacity; } private: + void notifyAddedTracks(std::size_t nbAddedTracks) const; Database::ObjectPtr getTrackList() const; bool isFull() const; diff --git a/src/lms/ui/explore/ArtistView.cpp b/src/lms/ui/explore/ArtistView.cpp index 778ca615..48f5cc9d 100644 --- a/src/lms/ui/explore/ArtistView.cpp +++ b/src/lms/ui/explore/ArtistView.cpp @@ -39,6 +39,7 @@ #include "Filters.hpp" #include "LmsApplication.hpp" #include "LmsApplicationException.hpp" +#include "PlayQueueController.hpp" #include "ReleaseListHelpers.hpp" #include "TrackListHelpers.hpp" #include "Utils.hpp" @@ -47,9 +48,10 @@ using namespace Database; namespace UserInterface { -Artist::Artist(Filters* filters) +Artist::Artist(Filters& filters, PlayQueueController& controller) : Template {Wt::WString::tr("Lms.Explore.Artist.template")} , _filters {filters} +, _playQueueController {controller} { addFunction("tr", &Wt::WTemplate::Functions::tr); addFunction("id", &Wt::WTemplate::Functions::id); @@ -59,7 +61,7 @@ Artist::Artist(Filters* filters) refreshView(); }); - filters->updated().connect([this] + filters.updated().connect([this] { refreshView(); }); @@ -140,7 +142,7 @@ Artist::refreshView() Wt::WInteractWidget* entry {clusterContainers->addWidget(Utils::createCluster(clusterId))}; entry->clicked().connect([=] { - _filters->add(clusterId); + _filters.add(clusterId); }); } } @@ -151,18 +153,18 @@ Artist::refreshView() bindNew("play-btn", Wt::WString::tr("Lms.Explore.play"), Wt::TextFormat::XHTML) ->clicked().connect([=] { - artistsAction.emit(PlayQueueAction::Play, {_artistId}); + _playQueueController.processCommand(PlayQueueController::Command::Play, {_artistId}); }); bindNew("play-shuffled", Wt::WString::tr("Lms.Explore.play-shuffled"), Wt::TextFormat::Plain) ->clicked().connect([=] { - artistsAction.emit(PlayQueueAction::PlayShuffled, {_artistId}); + _playQueueController.processCommand(PlayQueueController::Command::PlayShuffled, {_artistId}); }); bindNew("play-last", Wt::WString::tr("Lms.Explore.play-last"), Wt::TextFormat::Plain) ->clicked().connect([=] { - artistsAction.emit(PlayQueueAction::PlayLast, {_artistId}); + _playQueueController.processCommand(PlayQueueController::Command::PlayOrAddLast, {_artistId}); }); bindNew("download", Wt::WString::tr("Lms.Explore.download")) ->setLink(Wt::WLink {std::make_unique(*artistId)}); @@ -288,7 +290,7 @@ Artist::addSomeReleases(InfiniteScrollingContainer& releaseContainer, EnumSet(releaseContainer.getCount()), _releasesBatchSize}; Release::FindParameters params; - params.setClusters(_filters->getClusterIds()); + params.setClusters(_filters.getClusterIds()); params.setArtist(_artistId, linkTypes, excludedLinkTypes); params.setRange(range); params.setSortMethod(ReleaseSortMethod::DateDesc); @@ -315,7 +317,7 @@ Artist::addSomeNonReleaseTracks() const Range range {static_cast(_trackContainer->getCount()), _tracksBatchSize}; Track::FindParameters params; - params.setClusters(_filters->getClusterIds()); + params.setClusters(_filters.getClusterIds()); params.setArtist(_artistId); params.setRange(range); params.setSortMethod(TrackSortMethod::Name); @@ -333,7 +335,7 @@ Artist::addSomeNonReleaseTracks() } const Track::pointer track {Track::find(LmsApp->getDbSession(), trackId)}; - _trackContainer->add(TrackListHelpers::createEntry(track, tracksAction)); + _trackContainer->add(TrackListHelpers::createEntry(track, _playQueueController)); areTracksAdded = true; } diff --git a/src/lms/ui/explore/ArtistView.hpp b/src/lms/ui/explore/ArtistView.hpp index b1dd8cf5..d583a893 100644 --- a/src/lms/ui/explore/ArtistView.hpp +++ b/src/lms/ui/explore/ArtistView.hpp @@ -19,13 +19,12 @@ #pragma once +#include "services/database/ArtistId.hpp" #include "services/database/Object.hpp" #include "services/database/Types.hpp" #include "utils/EnumSet.hpp" #include "common/Template.hpp" -#include "PlayQueueAction.hpp" - namespace Database { class Artist; @@ -34,17 +33,14 @@ namespace Database namespace UserInterface { - class Filters; + class PlayQueueController; class InfiniteScrollingContainer; class Artist : public Template { public: - Artist(Filters* filters); - - PlayQueueActionArtistSignal artistsAction; - PlayQueueActionTrackSignal tracksAction; + Artist(Filters& filters, PlayQueueController& controller); private: void refreshView(); @@ -60,7 +56,8 @@ namespace UserInterface static constexpr std::size_t _tracksBatchSize {6}; static constexpr std::size_t _tracksMaxCount {160}; - Filters* _filters {}; + Filters& _filters; + PlayQueueController& _playQueueController; InfiniteScrollingContainer* _releaseContainer {}; InfiniteScrollingContainer* _appearsOnReleaseContainer {}; InfiniteScrollingContainer* _trackContainer {}; diff --git a/src/lms/ui/explore/Explore.cpp b/src/lms/ui/explore/Explore.cpp index be0a1ed2..1c0ed889 100644 --- a/src/lms/ui/explore/Explore.cpp +++ b/src/lms/ui/explore/Explore.cpp @@ -19,17 +19,8 @@ #include "Explore.hpp" +#include #include -#include -#include - -#include "services/database/Artist.hpp" -#include "services/database/Release.hpp" -#include "services/database/Session.hpp" -#include "services/database/Track.hpp" -#include "utils/Logger.hpp" - -#include "LmsApplication.hpp" #include "ArtistsView.hpp" #include "ArtistView.hpp" @@ -43,215 +34,96 @@ namespace UserInterface { -namespace { + namespace { -void -handleContentsPathChange(Wt::WStackedWidget* stack) -{ - enum Idx - { - IdxArtists = 0, - IdxArtist, - IdxTrackLists, - IdxTrackList, - IdxReleases, - IdxRelease, - IdxSearch, - IdxTracks, - }; - - static const std::map indexes = - { - { "/artists", IdxArtists }, - { "/artist", IdxArtist }, - { "/tracklists", IdxTrackLists }, - { "/tracklist", IdxTrackList }, - { "/releases", IdxReleases }, - { "/release", IdxRelease }, - { "/search", IdxSearch }, - { "/tracks", IdxTracks }, - }; - - for (const auto& index : indexes) - { - if (wApp->internalPathMatches(index.first)) + void + handleContentsPathChange(Wt::WStackedWidget* stack) { - stack->setCurrentIndex(index.second); - return; + enum Idx + { + IdxArtists = 0, + IdxArtist, + IdxTrackLists, + IdxTrackList, + IdxReleases, + IdxRelease, + IdxSearch, + IdxTracks, + }; + + static const std::map indexes = + { + { "/artists", IdxArtists }, + { "/artist", IdxArtist }, + { "/tracklists", IdxTrackLists }, + { "/tracklist", IdxTrackList }, + { "/releases", IdxReleases }, + { "/release", IdxRelease }, + { "/search", IdxSearch }, + { "/tracks", IdxTracks }, + }; + + for (const auto& index : indexes) + { + if (wApp->internalPathMatches(index.first)) + { + stack->setCurrentIndex(index.second); + return; + } + } } - } -} -} // namespace + } // namespace -Explore::Explore(Filters* filters) -: Wt::WTemplate {Wt::WString::tr("Lms.Explore.template")} -, _filters {filters} -{ - addFunction("tr", &Functions::tr); - - // Contents - Wt::WStackedWidget* contentsStack {bindNew("contents")}; - contentsStack->setOverflow(Wt::Overflow::Visible); // wt makes it hidden by default - - // same order as enum Idx - auto artists = std::make_unique(*_filters); - contentsStack->addWidget(std::move(artists)); - - auto artist = std::make_unique(_filters); - artist->artistsAction.connect(this, &Explore::handleArtistsAction); - artist->tracksAction.connect(this, &Explore::handleTracksAction); - contentsStack->addWidget(std::move(artist)); - - auto trackLists {std::make_unique(*_filters)}; - TrackLists* trackListsPtr {trackLists.get()}; - trackLists->trackListAction.connect(this, &Explore::handleTrackListAction); - contentsStack->addWidget(std::move(trackLists)); - - auto trackList {std::make_unique(*_filters)}; - TrackList* trackListPtr {trackList.get()}; - trackList->trackListAction.connect(this, &Explore::handleTrackListAction); - trackList->tracksAction.connect(this, &Explore::handleTracksAction); - contentsStack->addWidget(std::move(trackList)); - - trackListPtr->trackListDeleted.connect(trackListsPtr, &TrackLists::onTrackListDeleted); - - auto releases = std::make_unique(*_filters); - releases->releasesAction.connect(this, &Explore::handleReleasesAction); - contentsStack->addWidget(std::move(releases)); - - auto release = std::make_unique(_filters); - release->releasesAction.connect(this, &Explore::handleReleasesAction); - release->tracksAction.connect(this, &Explore::handleTracksAction); - contentsStack->addWidget(std::move(release)); - - auto search = std::make_unique(_filters); - search->tracksAction.connect(this, &Explore::handleTracksAction); - _search = search.get(); - contentsStack->addWidget(std::move(search)); - - auto tracks = std::make_unique(*_filters); - tracks->tracksAction.connect(this, &Explore::handleTracksAction); - contentsStack->addWidget(std::move(tracks)); - - wApp->internalPathChanged().connect(this, [=] + Explore::Explore(Filters& filters, PlayQueue& playQueue) + : Wt::WTemplate {Wt::WString::tr("Lms.Explore.template")} + , _playQueueController {filters, playQueue} { + addFunction("tr", &Functions::tr); + + // Contents + Wt::WStackedWidget* contentsStack {bindNew("contents")}; + contentsStack->setOverflow(Wt::Overflow::Visible); // wt makes it hidden by default + + // same order as enum Idx + auto artists = std::make_unique(filters); + contentsStack->addWidget(std::move(artists)); + + auto artist = std::make_unique(filters, _playQueueController); + contentsStack->addWidget(std::move(artist)); + + auto trackLists {std::make_unique(filters)}; + contentsStack->addWidget(std::move(trackLists)); + + auto trackList {std::make_unique(filters, _playQueueController)}; + trackList->trackListDeleted.connect(trackLists.get(), &TrackLists::onTrackListDeleted); + contentsStack->addWidget(std::move(trackList)); + + auto releases = std::make_unique(filters, _playQueueController); + contentsStack->addWidget(std::move(releases)); + + auto release = std::make_unique(filters, _playQueueController); + contentsStack->addWidget(std::move(release)); + + auto search = std::make_unique(filters, _playQueueController); + _search = search.get(); + contentsStack->addWidget(std::move(search)); + + auto tracks = std::make_unique(filters, _playQueueController); + contentsStack->addWidget(std::move(tracks)); + + wApp->internalPathChanged().connect(this, [=] + { + handleContentsPathChange(contentsStack); + }); + handleContentsPathChange(contentsStack); - }); - - handleContentsPathChange(contentsStack); -} - -void -Explore::search(const Wt::WString& searchText) -{ - _search->refreshView(searchText); -} - -static -std::vector -getArtistsTracks(Database::Session& session, const std::vector& artistsId, const std::vector& clusters, std::size_t maxTrackCount) -{ - assert(maxTrackCount); - - std::vector res; - - auto transaction {session.createSharedTransaction()}; - - for (const Database::ArtistId artistId : artistsId) - { - Database::Track::FindParameters params; - params.setArtist(artistId); - params.setSortMethod(Database::TrackSortMethod::DateDescAndRelease); - params.setClusters(clusters); - params.setRange({0, maxTrackCount - res.size()}); - - const auto tracks {Database::Track::find(session, params)}; - - res.reserve(res.size() + tracks.results.size()); - res.insert(std::end(res), std::cbegin(tracks.results), std::cend(tracks.results)); - - if (res.size() == maxTrackCount) - break; } - return res; -} - -static -std::vector -getReleasesTracks(Database::Session& session, const std::vector& releasesId, const std::vector& clusters, std::size_t maxTrackCount) -{ - using namespace Database; - assert(maxTrackCount); - - std::vector res; - - auto transaction {session.createSharedTransaction()}; - - for (const ReleaseId releaseId : releasesId) + void + Explore::search(const Wt::WString& searchText) { - Database::Track::FindParameters params; - params.setRelease(releaseId); - params.setSortMethod(Database::TrackSortMethod::Release); - params.setClusters(clusters); - params.setRange({0, maxTrackCount - res.size()}); - - const auto tracks {Database::Track::find(session, params)}; - - res.reserve(res.size() + tracks.results.size()); - res.insert(std::end(res), std::cbegin(tracks.results), std::cend(tracks.results)); - - if (res.size() == maxTrackCount) - break; + _search->refreshView(searchText); } - return res; -} - -static -std::vector -getTrackListTracks(Database::Session& session, Database::TrackListId trackListId, const std::vector& clusters, std::size_t maxTrackCount) -{ - using namespace Database; - assert(maxTrackCount); - - auto transaction {session.createSharedTransaction()}; - - Database::Track::FindParameters params; - params.setTrackList(trackListId); - params.setClusters(clusters); - params.setRange({0, maxTrackCount}); - params.setSortMethod(TrackSortMethod::TrackList); - params.setDistinct(false); - - return Database::Track::find(session, params).results; -} - -void -Explore::handleArtistsAction(PlayQueueAction action, const std::vector& artistsId) -{ - tracksAction.emit(action, getArtistsTracks(LmsApp->getDbSession(), artistsId, _filters->getClusterIds(), _maxTrackCount)); -} - -void -Explore::handleReleasesAction(PlayQueueAction action, const std::vector& releasesId) -{ - tracksAction.emit(action, getReleasesTracks(LmsApp->getDbSession(), releasesId, _filters->getClusterIds(), _maxTrackCount)); -} - -void -Explore::handleTrackListAction(PlayQueueAction action, Database::TrackListId trackListId) -{ - tracksAction.emit(action, getTrackListTracks(LmsApp->getDbSession(), trackListId, _filters->getClusterIds(), _maxTrackCount)); -} - -void -Explore::handleTracksAction(PlayQueueAction action, const std::vector& tracksId) -{ - // consider things are already filtered here, and _maxTrackCount honored playqueue side... - tracksAction.emit(action, tracksId); -} - } // namespace UserInterface - diff --git a/src/lms/ui/explore/Explore.hpp b/src/lms/ui/explore/Explore.hpp index 96362571..d06c7c48 100644 --- a/src/lms/ui/explore/Explore.hpp +++ b/src/lms/ui/explore/Explore.hpp @@ -20,34 +20,25 @@ #pragma once #include - -#include "services/database/Types.hpp" -#include "PlayQueueAction.hpp" +#include "PlayQueueController.hpp" namespace UserInterface { class Filters; class SearchView; + class PlayQueue; class Explore : public Wt::WTemplate { public: - Explore(Filters* filters); + Explore(Filters& filters, PlayQueue& playQueue); void search(const Wt::WString& searchText); - - PlayQueueActionTrackSignal tracksAction; - void setMaxTrackCountForAction(std::size_t maxTrackCount) { _maxTrackCount = maxTrackCount; } + PlayQueueController& getPlayQueueController() { return _playQueueController; } private: - void handleArtistsAction(PlayQueueAction action, const std::vector& artistsId); - void handleReleasesAction(PlayQueueAction action, const std::vector& releasesId); - void handleTrackListAction(PlayQueueAction action, Database::TrackListId trackiListId); - void handleTracksAction(PlayQueueAction action, const std::vector& tracksId); - - Filters* _filters {}; + PlayQueueController _playQueueController; SearchView* _search {}; - std::size_t _maxTrackCount {}; }; } // namespace UserInterface diff --git a/src/lms/ui/explore/PlayQueueController.cpp b/src/lms/ui/explore/PlayQueueController.cpp new file mode 100644 index 00000000..4f07d706 --- /dev/null +++ b/src/lms/ui/explore/PlayQueueController.cpp @@ -0,0 +1,179 @@ +/* + * Copyright (C) 2022 Emeric Poupon + * + * This file is part of LMS. + * + * LMS is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * LMS 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with LMS. If not, see . + */ + +#include "services/database/ClusterId.hpp" +#include "services/database/Release.hpp" +#include "services/database/Session.hpp" +#include "services/database/Track.hpp" +#include "explore/Filters.hpp" +#include "explore/PlayQueueController.hpp" +#include "PlayQueue.hpp" +#include "LmsApplication.hpp" + +namespace UserInterface +{ + + static + std::vector + getArtistsTracks(Database::Session& session, const std::vector& artistsId, const std::vector& clusters, std::size_t maxTrackCount) + { + assert(maxTrackCount); + + std::vector res; + + auto transaction {session.createSharedTransaction()}; + + for (const Database::ArtistId artistId : artistsId) + { + Database::Track::FindParameters params; + params.setArtist(artistId); + params.setSortMethod(Database::TrackSortMethod::DateDescAndRelease); + params.setClusters(clusters); + params.setRange({0, maxTrackCount - res.size()}); + + const auto tracks {Database::Track::find(session, params)}; + + res.reserve(res.size() + tracks.results.size()); + res.insert(std::end(res), std::cbegin(tracks.results), std::cend(tracks.results)); + + if (res.size() == maxTrackCount) + break; + } + + return res; + } + + static + std::vector + getReleasesTracks(Database::Session& session, const std::vector& releasesId, const std::vector& clusters, std::size_t maxTrackCount) + { + using namespace Database; + assert(maxTrackCount); + + std::vector res; + + auto transaction {session.createSharedTransaction()}; + + for (const ReleaseId releaseId : releasesId) + { + Database::Track::FindParameters params; + params.setRelease(releaseId); + params.setSortMethod(Database::TrackSortMethod::Release); + params.setClusters(clusters); + params.setRange({0, maxTrackCount - res.size()}); + + const auto tracks {Database::Track::find(session, params)}; + + res.reserve(res.size() + tracks.results.size()); + res.insert(std::end(res), std::cbegin(tracks.results), std::cend(tracks.results)); + + if (res.size() == maxTrackCount) + break; + } + + return res; + } + + static + std::vector + getTrackListTracks(Database::Session& session, Database::TrackListId trackListId, const std::vector& clusters, std::size_t maxTrackCount) + { + using namespace Database; + assert(maxTrackCount); + + auto transaction {session.createSharedTransaction()}; + + Database::Track::FindParameters params; + params.setTrackList(trackListId); + params.setClusters(clusters); + params.setRange({0, maxTrackCount}); + params.setSortMethod(TrackSortMethod::TrackList); + params.setDistinct(false); + + return Database::Track::find(session, params).results; + } + + PlayQueueController::PlayQueueController(Filters& filters, PlayQueue& playQueue) + : _filters {filters} + , _playQueue {playQueue} + { + } + + void + PlayQueueController::processCommand(Command command, const std::vector& artistIds) + { + const std::vector tracks {getArtistsTracks(LmsApp->getDbSession(), artistIds, _filters.getClusterIds(), _maxTrackCountToEnqueue)}; + processCommand(command, tracks); + + } + + void + PlayQueueController::processCommand(Command command, const std::vector& releaseIds) + { + const std::vector tracks {getReleasesTracks(LmsApp->getDbSession(), releaseIds, _filters.getClusterIds(), _maxTrackCountToEnqueue)}; + processCommand(command, tracks); + } + + void + PlayQueueController::processCommand(Command command, const std::vector& trackIds) + { + // consider things are already filtered here, and _maxTrackCount honored playqueue side... + switch (command) + { + case Command::Play: + _playQueue.play(trackIds); + break; + case Command::PlayShuffled: + _playQueue.playShuffled(trackIds); + break; + case Command::PlayOrAddLast: + _playQueue.playOrAddLast(trackIds); + break; + } + } + + void + PlayQueueController::processCommand(Command command, Database::TrackListId trackListId) + { + const std::vector tracks {getTrackListTracks(LmsApp->getDbSession(), trackListId, _filters.getClusterIds(), _maxTrackCountToEnqueue)}; + processCommand(command, tracks); + } + + void + PlayQueueController::playTrackInRelease(Database::TrackId trackId) + { + Database::ReleaseId releaseId; + { + auto transaction {LmsApp->getDbSession().createSharedTransaction()}; + const Database::Track::pointer track {Database::Track::find(LmsApp->getDbSession(), trackId)}; + if (!track || !track->getRelease()) + return; + + releaseId = track->getRelease()->getId(); + } + + const std::vector tracks {getReleasesTracks(LmsApp->getDbSession(), {releaseId}, _filters.getClusterIds(), _maxTrackCountToEnqueue)}; + auto itTrack {std::find(std::cbegin(tracks), std::cend(tracks), trackId)}; + if (itTrack == std::cend(tracks)) + return; + + const std::size_t index {static_cast(std::distance(std::cbegin(tracks), itTrack))}; + _playQueue.playAtIndex(tracks, index); + } +} // namespace UserInterface diff --git a/src/lms/ui/PlayQueueAction.hpp b/src/lms/ui/explore/PlayQueueController.hpp similarity index 50% rename from src/lms/ui/PlayQueueAction.hpp rename to src/lms/ui/explore/PlayQueueController.hpp index 5a3bc05e..6bb9baf7 100644 --- a/src/lms/ui/PlayQueueAction.hpp +++ b/src/lms/ui/explore/PlayQueueController.hpp @@ -1,5 +1,5 @@ /* - * Copyright (C) 2020 Emeric Poupon + * Copyright (C) 2022 Emeric Poupon * * This file is part of LMS. * @@ -20,7 +20,6 @@ #pragma once #include -#include #include "services/database/ArtistId.hpp" #include "services/database/ReleaseId.hpp" @@ -29,16 +28,34 @@ namespace UserInterface { - enum class PlayQueueAction - { - Play, - PlayLast, - PlayShuffled, - }; + class Filters; + class PlayQueue; - using PlayQueueActionArtistSignal = Wt::Signal&>; - using PlayQueueActionReleaseSignal = Wt::Signal&>; - using PlayQueueActionTrackSignal = Wt::Signal&>; - using PlayQueueActionTrackListSignal = Wt::Signal; + // Used to interact with the play queue, using the current exploration filters + class PlayQueueController + { + public: + PlayQueueController(Filters& filters, PlayQueue& playQueue); + + enum class Command + { + Play, + PlayOrAddLast, + PlayShuffled, + }; + + void processCommand(Command command, const std::vector&); + void processCommand(Command command, const std::vector&); + void processCommand(Command command, const std::vector&); + void processCommand(Command command, Database::TrackListId); + void playTrackInRelease(Database::TrackId); + + void setMaxTrackCountToEnqueue(std::size_t maxTrackCount) { _maxTrackCountToEnqueue = maxTrackCount; } + + private: + Filters& _filters; + PlayQueue& _playQueue; + std::size_t _maxTrackCountToEnqueue {}; + }; } diff --git a/src/lms/ui/explore/ReleaseView.cpp b/src/lms/ui/explore/ReleaseView.cpp index f003cebe..3c0c6094 100644 --- a/src/lms/ui/explore/ReleaseView.cpp +++ b/src/lms/ui/explore/ReleaseView.cpp @@ -34,20 +34,22 @@ #include "common/Template.hpp" #include "resource/DownloadResource.hpp" -#include "Filters.hpp" +#include "explore/Filters.hpp" +#include "explore/PlayQueueController.hpp" +#include "explore/ReleaseListHelpers.hpp" #include "LmsApplication.hpp" #include "LmsApplicationException.hpp" #include "MediaPlayer.hpp" -#include "ReleaseListHelpers.hpp" #include "Utils.hpp" using namespace Database; namespace UserInterface { -Release::Release(Filters* filters) +Release::Release(Filters& filters, PlayQueueController& playQueueController) : Template {Wt::WString::tr("Lms.Explore.Release.template")} , _filters {filters} +, _playQueueController {playQueueController} { addFunction("tr", &Wt::WTemplate::Functions::tr); addFunction("id", &Wt::WTemplate::Functions::id); @@ -57,7 +59,7 @@ Release::Release(Filters* filters) refreshView(); }); - filters->updated().connect([this] + _filters.updated().connect([this] { refreshView(); }); @@ -147,7 +149,7 @@ Release::refreshView() Wt::WInteractWidget* entry {clusterContainers->addWidget(Utils::createCluster(clusterId))}; entry->clicked().connect([=] { - _filters->add(clusterId); + _filters.add(clusterId); }); } } @@ -156,19 +158,19 @@ Release::refreshView() bindNew("play-btn", Wt::WString::tr("Lms.Explore.play"), Wt::TextFormat::XHTML) ->clicked().connect([=] { - releasesAction.emit(PlayQueueAction::Play, {*releaseId}); + _playQueueController.processCommand(PlayQueueController::Command::Play, {*releaseId}); }); bindNew("play-shuffled", Wt::WString::tr("Lms.Explore.play-shuffled"), Wt::TextFormat::Plain) ->clicked().connect([=] { - releasesAction.emit(PlayQueueAction::PlayShuffled, {*releaseId}); + _playQueueController.processCommand(PlayQueueController::Command::PlayShuffled, {*releaseId}); }); bindNew("play-last", Wt::WString::tr("Lms.Explore.play-last"), Wt::TextFormat::Plain) ->clicked().connect([=] { - releasesAction.emit(PlayQueueAction::PlayLast, {*releaseId}); + _playQueueController.processCommand(PlayQueueController::Command::PlayOrAddLast, {*releaseId}); }); bindNew("download", Wt::WString::tr("Lms.Explore.download")) @@ -238,7 +240,7 @@ Release::refreshView() Database::Track::FindParameters params; params.setRelease(*releaseId); params.setSortMethod(Database::TrackSortMethod::Release); - params.setClusters(_filters->getClusterIds()); + params.setClusters(_filters.getClusterIds()); const auto tracks {Database::Track::find(LmsApp->getDbSession(), params)}; for (const Database::TrackId trackId : tracks.results) @@ -285,7 +287,7 @@ Release::refreshView() Wt::WPushButton* playBtn {entry->bindNew("play-btn", Wt::WString::tr("Lms.template.play-btn"), Wt::TextFormat::XHTML)}; playBtn->clicked().connect([=] { - tracksAction.emit(PlayQueueAction::Play, {trackId}); + _playQueueController.playTrackInRelease(trackId); }); { @@ -293,7 +295,7 @@ Release::refreshView() entry->bindNew("play-last", Wt::WString::tr("Lms.Explore.play-last")) ->clicked().connect([=] { - tracksAction.emit(PlayQueueAction::PlayLast, {trackId}); + _playQueueController.processCommand(PlayQueueController::Command::PlayOrAddLast, {trackId}); }); auto isStarred {[=] { return Service::get()->isStarred(LmsApp->getUserId(), trackId); }}; diff --git a/src/lms/ui/explore/ReleaseView.hpp b/src/lms/ui/explore/ReleaseView.hpp index e867874f..91964f33 100644 --- a/src/lms/ui/explore/ReleaseView.hpp +++ b/src/lms/ui/explore/ReleaseView.hpp @@ -20,8 +20,8 @@ #pragma once #include "services/database/Object.hpp" +#include "services/database/ReleaseId.hpp" #include "common/Template.hpp" -#include "PlayQueueAction.hpp" namespace Database { @@ -31,15 +31,12 @@ namespace Database namespace UserInterface { class Filters; - + class PlayQueueController; class Release : public Template { public: - Release(Filters* filters); - - PlayQueueActionReleaseSignal releasesAction; - PlayQueueActionTrackSignal tracksAction; + Release(Filters& filters, PlayQueueController& playQueueController); private: void refreshView(); @@ -48,7 +45,8 @@ namespace UserInterface void refreshLinks(const Database::ObjectPtr& release); void refreshSimilarReleases(const std::vector& similarReleasesId); - Filters* _filters {}; + Filters& _filters; + PlayQueueController& _playQueueController; }; } // namespace UserInterface diff --git a/src/lms/ui/explore/ReleasesView.cpp b/src/lms/ui/explore/ReleasesView.cpp index e70ceef2..4d3cd927 100644 --- a/src/lms/ui/explore/ReleasesView.cpp +++ b/src/lms/ui/explore/ReleasesView.cpp @@ -26,16 +26,18 @@ #include "common/InfiniteScrollingContainer.hpp" #include "common/Template.hpp" -#include "ReleaseListHelpers.hpp" -#include "Filters.hpp" +#include "explore/Filters.hpp" +#include "explore/PlayQueueController.hpp" +#include "explore/ReleaseListHelpers.hpp" #include "LmsApplication.hpp" using namespace Database; namespace UserInterface { -Releases::Releases(Filters& filters) +Releases::Releases(Filters& filters, PlayQueueController& playQueueController) : Template {Wt::WString::tr("Lms.Explore.Releases.template")} +, _playQueueController {playQueueController} , _releaseCollector {filters, _defaultMode, _maxCount} { addFunction("tr", &Wt::WTemplate::Functions::tr); @@ -69,18 +71,18 @@ Releases::Releases(Filters& filters) Wt::WPushButton* playBtn {bindNew("play-btn", Wt::WString::tr("Lms.Explore.play"), Wt::TextFormat::XHTML)}; playBtn->clicked().connect([this] { - releasesAction.emit(PlayQueueAction::Play, getAllReleases()); + _playQueueController.processCommand(PlayQueueController::Command::Play, getAllReleases()); }); bindNew("play-shuffled", Wt::WString::tr("Lms.Explore.play-shuffled"), Wt::TextFormat::Plain) ->clicked().connect([=] { - releasesAction.emit(PlayQueueAction::PlayShuffled, getAllReleases()); + _playQueueController.processCommand(PlayQueueController::Command::PlayShuffled, getAllReleases()); }); bindNew("play-last", Wt::WString::tr("Lms.Explore.play-last"), Wt::TextFormat::Plain) ->clicked().connect([=] { - releasesAction.emit(PlayQueueAction::PlayLast, getAllReleases()); + _playQueueController.processCommand(PlayQueueController::Command::PlayOrAddLast, getAllReleases()); }); _container = bindNew("releases", Wt::WString::tr("Lms.Explore.Releases.template.container")); diff --git a/src/lms/ui/explore/ReleasesView.hpp b/src/lms/ui/explore/ReleasesView.hpp index b085c307..116a1390 100644 --- a/src/lms/ui/explore/ReleasesView.hpp +++ b/src/lms/ui/explore/ReleasesView.hpp @@ -22,20 +22,18 @@ #include "services/database/Types.hpp" #include "common/Template.hpp" -#include "PlayQueueAction.hpp" #include "ReleaseCollector.hpp" namespace UserInterface { class Filters; class InfiniteScrollingContainer; + class PlayQueueController; class Releases : public Template { public: - Releases(Filters& filters); - - PlayQueueActionReleaseSignal releasesAction; + Releases(Filters& filters, PlayQueueController& playQueueController); private: void refreshView(); @@ -48,6 +46,7 @@ namespace UserInterface static constexpr std::size_t _batchSize {_maxItemsPerLine}; static constexpr std::size_t _maxCount {_maxItemsPerLine * 500}; + PlayQueueController& _playQueueController; Wt::WWidget* _currentActiveItem {}; InfiniteScrollingContainer* _container {}; ReleaseCollector _releaseCollector; diff --git a/src/lms/ui/explore/SearchView.cpp b/src/lms/ui/explore/SearchView.cpp index 42770b6d..b01030c5 100644 --- a/src/lms/ui/explore/SearchView.cpp +++ b/src/lms/ui/explore/SearchView.cpp @@ -35,12 +35,12 @@ using namespace Database; namespace UserInterface { - SearchView::SearchView(Filters* filters) + SearchView::SearchView(Filters& filters, PlayQueueController& playQueueController) : Wt::WTemplate {Wt::WString::tr("Lms.Explore.Search.template")} - , _filters {filters} - , _artistCollector {*filters, ArtistCollector::Mode::Search, getMaxCount(Mode::Artist)} - , _releaseCollector {*filters, ReleaseCollector::Mode::Search, getMaxCount(Mode::Release)} - , _trackCollector {*filters, TrackCollector::Mode::Search, getMaxCount(Mode::Track)} + , _playQueueController {playQueueController} + , _artistCollector {filters, ArtistCollector::Mode::Search, getMaxCount(Mode::Artist)} + , _releaseCollector {filters, ReleaseCollector::Mode::Search, getMaxCount(Mode::Release)} + , _trackCollector {filters, TrackCollector::Mode::Search, getMaxCount(Mode::Track)} { addFunction("tr", &Wt::WTemplate::Functions::tr); @@ -53,7 +53,7 @@ namespace UserInterface _tracks = bindNew("tracks"); _tracks->onRequestElements.connect([this] { addSomeTracks(); }); - _filters->updated().connect([=] + filters.updated().connect([=] { refreshView(); }); @@ -152,7 +152,7 @@ namespace UserInterface for (const TrackId trackId : trackIds.results) { const Track::pointer track {Track::find(LmsApp->getDbSession(), trackId)}; - _tracks->add(TrackListHelpers::createEntry(track, tracksAction)); + _tracks->add(TrackListHelpers::createEntry(track, _playQueueController)); } } diff --git a/src/lms/ui/explore/SearchView.hpp b/src/lms/ui/explore/SearchView.hpp index 36bd2653..9c22aa6e 100644 --- a/src/lms/ui/explore/SearchView.hpp +++ b/src/lms/ui/explore/SearchView.hpp @@ -27,20 +27,17 @@ #include "ArtistCollector.hpp" #include "ReleaseCollector.hpp" #include "TrackCollector.hpp" -#include "PlayQueueAction.hpp" namespace UserInterface { - class InfiniteScrollingContainer; class Filters; + class PlayQueueController; class SearchView : public Wt::WTemplate { public: - SearchView(Filters* filters); - - PlayQueueActionTrackSignal tracksAction; + SearchView(Filters& filters, PlayQueueController& playQueueController); void refreshView(const Wt::WString& searchText); @@ -74,7 +71,7 @@ namespace UserInterface void addSomeReleases(); void addSomeTracks(); - Filters* _filters {}; + PlayQueueController& _playQueueController; ArtistCollector _artistCollector; ReleaseCollector _releaseCollector; TrackCollector _trackCollector; diff --git a/src/lms/ui/explore/TrackListHelpers.cpp b/src/lms/ui/explore/TrackListHelpers.cpp index 71ad71e7..c296df60 100644 --- a/src/lms/ui/explore/TrackListHelpers.cpp +++ b/src/lms/ui/explore/TrackListHelpers.cpp @@ -20,7 +20,6 @@ #include "TrackListHelpers.hpp" #include -#include #include #include @@ -29,10 +28,10 @@ #include "services/scrobbling/IScrobblingService.hpp" #include "services/database/Session.hpp" #include "services/database/Track.hpp" -#include "utils/Logger.hpp" #include "utils/Service.hpp" #include "common/Template.hpp" +#include "explore/PlayQueueController.hpp" #include "resource/DownloadResource.hpp" #include "resource/CoverResource.hpp" #include "LmsApplication.hpp" @@ -44,7 +43,7 @@ using namespace Database; namespace UserInterface::TrackListHelpers { std::unique_ptr - createEntry(const Database::ObjectPtr& track, PlayQueueActionTrackSignal& tracksAction) + createEntry(const Database::ObjectPtr& track, PlayQueueController& playQueueController) { auto entry {std::make_unique