Playing a track in the album view will now play the whole album at the selected track index (+ reworked play queue controller)

This commit is contained in:
emeric
2022-09-07 18:35:15 +02:00
parent 915e114933
commit 43aa2ece3c
23 changed files with 434 additions and 367 deletions
+1
View File
@@ -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
+4 -7
View File
@@ -502,8 +502,9 @@ LmsApplication::createHome()
Wt::WStackedWidget* mainStack {main->bindNew<Wt::WStackedWidget>("contents")};
mainStack->setOverflow(Wt::Overflow::Visible); // wt makes it hidden by default
Explore* explore {mainStack->addNew<Explore>(filters)};
_playQueue = mainStack->addNew<PlayQueue>();
std::unique_ptr<PlayQueue> playQueue {std::make_unique<PlayQueue>()};
Explore* explore {mainStack->addNew<Explore>(*filters, *playQueue)};
_playQueue = mainStack->addWidget(std::move(playQueue));
mainStack->addNew<SettingsView>();
searchEdit->enterPressed().connect([=]
@@ -525,11 +526,7 @@ LmsApplication::createHome()
mainStack->addNew<UserView>();
}
explore->setMaxTrackCountForAction(_playQueue->getCapacity());
explore->tracksAction.connect([this] (PlayQueueAction action, const std::vector<Database::TrackId>& trackIds)
{
_playQueue->processTracks(action, trackIds);
});
explore->getPlayQueueController().setMaxTrackCountToEnqueue(_playQueue->getCapacity());
// Events from MediaPlayer
_mediaPlayer->playNext.connect([this]
+34 -26
View File
@@ -416,38 +416,46 @@ PlayQueue::enqueueTracks(const std::vector<Database::TrackId>& trackIds)
}
void
PlayQueue::processTracks(PlayQueueAction action, const std::vector<Database::TrackId>& trackIds)
PlayQueue::play(const std::vector<Database::TrackId>& 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<Database::TrackId>& trackIds)
{
clearTracks();
std::vector<Database::TrackId> 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<Database::TrackId>& trackIds)
{
const std::size_t nbAddedTracks {enqueueTracks(trackIds)};
if (!_trackPos)
loadTrack(0, true);
case PlayQueueAction::PlayShuffled:
{
clearTracks();
{
std::vector<Database::TrackId> shuffledTrackIds {trackIds};
Random::shuffleContainer(shuffledTrackIds);
nbAddedTracks = enqueueTracks(shuffledTrackIds);
}
loadTrack(0, true);
break;
}
}
notifyAddedTracks(nbAddedTracks);
}
void
PlayQueue::playAtIndex(const std::vector<Database::TrackId>& 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});
+6 -2
View File
@@ -27,8 +27,8 @@
#include <Wt/WText.h>
#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<Database::TrackId>& trackIds);
void play(const std::vector<Database::TrackId>& trackIds);
void playShuffled(const std::vector<Database::TrackId>& trackIds);
void playOrAddLast(const std::vector<Database::TrackId>& trackIds); // play if queue empty, otherwise just add last
void playAtIndex(const std::vector<Database::TrackId>& 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<Database::TrackList> getTrackList() const;
bool isFull() const;
+11 -9
View File
@@ -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<Wt::WPushButton>("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<Wt::WPushButton>("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<Wt::WPushButton>("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<Wt::WPushButton>("download", Wt::WString::tr("Lms.Explore.download"))
->setLink(Wt::WLink {std::make_unique<DownloadArtistResource>(*artistId)});
@@ -288,7 +290,7 @@ Artist::addSomeReleases(InfiniteScrollingContainer& releaseContainer, EnumSet<Da
const Range range {static_cast<std::size_t>(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<std::size_t>(_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;
}
+5 -8
View File
@@ -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 {};
+81 -209
View File
@@ -19,17 +19,8 @@
#include "Explore.hpp"
#include <Wt/WApplication.h>
#include <Wt/WStackedWidget.h>
#include <Wt/WTemplate.h>
#include <Wt/WText.h>
#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<std::string, int> 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<std::string, int> 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<Wt::WStackedWidget>("contents")};
contentsStack->setOverflow(Wt::Overflow::Visible); // wt makes it hidden by default
// same order as enum Idx
auto artists = std::make_unique<Artists>(*_filters);
contentsStack->addWidget(std::move(artists));
auto artist = std::make_unique<Artist>(_filters);
artist->artistsAction.connect(this, &Explore::handleArtistsAction);
artist->tracksAction.connect(this, &Explore::handleTracksAction);
contentsStack->addWidget(std::move(artist));
auto trackLists {std::make_unique<TrackLists>(*_filters)};
TrackLists* trackListsPtr {trackLists.get()};
trackLists->trackListAction.connect(this, &Explore::handleTrackListAction);
contentsStack->addWidget(std::move(trackLists));
auto trackList {std::make_unique<TrackList>(*_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<Releases>(*_filters);
releases->releasesAction.connect(this, &Explore::handleReleasesAction);
contentsStack->addWidget(std::move(releases));
auto release = std::make_unique<Release>(_filters);
release->releasesAction.connect(this, &Explore::handleReleasesAction);
release->tracksAction.connect(this, &Explore::handleTracksAction);
contentsStack->addWidget(std::move(release));
auto search = std::make_unique<SearchView>(_filters);
search->tracksAction.connect(this, &Explore::handleTracksAction);
_search = search.get();
contentsStack->addWidget(std::move(search));
auto tracks = std::make_unique<Tracks>(*_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<Wt::WStackedWidget>("contents")};
contentsStack->setOverflow(Wt::Overflow::Visible); // wt makes it hidden by default
// same order as enum Idx
auto artists = std::make_unique<Artists>(filters);
contentsStack->addWidget(std::move(artists));
auto artist = std::make_unique<Artist>(filters, _playQueueController);
contentsStack->addWidget(std::move(artist));
auto trackLists {std::make_unique<TrackLists>(filters)};
contentsStack->addWidget(std::move(trackLists));
auto trackList {std::make_unique<TrackList>(filters, _playQueueController)};
trackList->trackListDeleted.connect(trackLists.get(), &TrackLists::onTrackListDeleted);
contentsStack->addWidget(std::move(trackList));
auto releases = std::make_unique<Releases>(filters, _playQueueController);
contentsStack->addWidget(std::move(releases));
auto release = std::make_unique<Release>(filters, _playQueueController);
contentsStack->addWidget(std::move(release));
auto search = std::make_unique<SearchView>(filters, _playQueueController);
_search = search.get();
contentsStack->addWidget(std::move(search));
auto tracks = std::make_unique<Tracks>(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<Database::TrackId>
getArtistsTracks(Database::Session& session, const std::vector<Database::ArtistId>& artistsId, const std::vector<Database::ClusterId>& clusters, std::size_t maxTrackCount)
{
assert(maxTrackCount);
std::vector<Database::TrackId> 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<Database::TrackId>
getReleasesTracks(Database::Session& session, const std::vector<Database::ReleaseId>& releasesId, const std::vector<Database::ClusterId>& clusters, std::size_t maxTrackCount)
{
using namespace Database;
assert(maxTrackCount);
std::vector<TrackId> 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<Database::TrackId>
getTrackListTracks(Database::Session& session, Database::TrackListId trackListId, const std::vector<Database::ClusterId>& 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<Database::ArtistId>& artistsId)
{
tracksAction.emit(action, getArtistsTracks(LmsApp->getDbSession(), artistsId, _filters->getClusterIds(), _maxTrackCount));
}
void
Explore::handleReleasesAction(PlayQueueAction action, const std::vector<Database::ReleaseId>& 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<Database::TrackId>& tracksId)
{
// consider things are already filtered here, and _maxTrackCount honored playqueue side...
tracksAction.emit(action, tracksId);
}
} // namespace UserInterface
+5 -14
View File
@@ -20,34 +20,25 @@
#pragma once
#include <Wt/WTemplate.h>
#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<Database::ArtistId>& artistsId);
void handleReleasesAction(PlayQueueAction action, const std::vector<Database::ReleaseId>& releasesId);
void handleTrackListAction(PlayQueueAction action, Database::TrackListId trackiListId);
void handleTracksAction(PlayQueueAction action, const std::vector<Database::TrackId>& tracksId);
Filters* _filters {};
PlayQueueController _playQueueController;
SearchView* _search {};
std::size_t _maxTrackCount {};
};
} // namespace UserInterface
+179
View File
@@ -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 <http://www.gnu.org/licenses/>.
*/
#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<Database::TrackId>
getArtistsTracks(Database::Session& session, const std::vector<Database::ArtistId>& artistsId, const std::vector<Database::ClusterId>& clusters, std::size_t maxTrackCount)
{
assert(maxTrackCount);
std::vector<Database::TrackId> 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<Database::TrackId>
getReleasesTracks(Database::Session& session, const std::vector<Database::ReleaseId>& releasesId, const std::vector<Database::ClusterId>& clusters, std::size_t maxTrackCount)
{
using namespace Database;
assert(maxTrackCount);
std::vector<TrackId> 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<Database::TrackId>
getTrackListTracks(Database::Session& session, Database::TrackListId trackListId, const std::vector<Database::ClusterId>& 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<Database::ArtistId>& artistIds)
{
const std::vector<Database::TrackId> tracks {getArtistsTracks(LmsApp->getDbSession(), artistIds, _filters.getClusterIds(), _maxTrackCountToEnqueue)};
processCommand(command, tracks);
}
void
PlayQueueController::processCommand(Command command, const std::vector<Database::ReleaseId>& releaseIds)
{
const std::vector<Database::TrackId> tracks {getReleasesTracks(LmsApp->getDbSession(), releaseIds, _filters.getClusterIds(), _maxTrackCountToEnqueue)};
processCommand(command, tracks);
}
void
PlayQueueController::processCommand(Command command, const std::vector<Database::TrackId>& 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<Database::TrackId> 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<Database::TrackId> 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::size_t>(std::distance(std::cbegin(tracks), itTrack))};
_playQueue.playAtIndex(tracks, index);
}
} // namespace UserInterface
@@ -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 <vector>
#include <Wt/WSignal.h>
#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<PlayQueueAction, const std::vector<Database::ArtistId>&>;
using PlayQueueActionReleaseSignal = Wt::Signal<PlayQueueAction, const std::vector<Database::ReleaseId>&>;
using PlayQueueActionTrackSignal = Wt::Signal<PlayQueueAction, const std::vector<Database::TrackId>&>;
using PlayQueueActionTrackListSignal = Wt::Signal<PlayQueueAction, Database::TrackListId>;
// 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<Database::ArtistId>&);
void processCommand(Command command, const std::vector<Database::ReleaseId>&);
void processCommand(Command command, const std::vector<Database::TrackId>&);
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 {};
};
}
+13 -11
View File
@@ -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<Wt::WPushButton>("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<Wt::WPushButton>("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<Wt::WPushButton>("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<Wt::WPushButton>("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<Wt::WPushButton>("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<Wt::WPushButton>("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<Scrobbling::IScrobblingService>::get()->isStarred(LmsApp->getUserId(), trackId); }};
+5 -7
View File
@@ -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<Database::Release>& release);
void refreshSimilarReleases(const std::vector<Database::ReleaseId>& similarReleasesId);
Filters* _filters {};
Filters& _filters;
PlayQueueController& _playQueueController;
};
} // namespace UserInterface
+8 -6
View File
@@ -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<Wt::WPushButton>("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<Wt::WPushButton>("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<Wt::WPushButton>("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<InfiniteScrollingContainer>("releases", Wt::WString::tr("Lms.Explore.Releases.template.container"));
+3 -4
View File
@@ -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;
+7 -7
View File
@@ -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<InfiniteScrollingContainer>("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));
}
}
+3 -6
View File
@@ -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;
+6 -7
View File
@@ -20,7 +20,6 @@
#include "TrackListHelpers.hpp"
#include <Wt/WAnchor.h>
#include <Wt/WContainerWidget.h>
#include <Wt/WImage.h>
#include <Wt/WPushButton.h>
@@ -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<Wt::WWidget>
createEntry(const Database::ObjectPtr<Database::Track>& track, PlayQueueActionTrackSignal& tracksAction)
createEntry(const Database::ObjectPtr<Database::Track>& track, PlayQueueController& playQueueController)
{
auto entry {std::make_unique<Template>(Wt::WString::tr("Lms.Explore.Tracks.template.entry"))};
auto* entryPtr {entry.get()};
@@ -74,16 +73,16 @@ namespace UserInterface::TrackListHelpers
entry->bindString("duration", Utils::durationToString(track->getDuration()), Wt::TextFormat::Plain);
Wt::WPushButton* playBtn {entry->bindNew<Wt::WPushButton>("play-btn", Wt::WString::tr("Lms.template.play-btn"), Wt::TextFormat::XHTML)};
playBtn->clicked().connect([trackId, &tracksAction]
playBtn->clicked().connect([trackId, &playQueueController]
{
tracksAction.emit(PlayQueueAction::Play, {trackId});
playQueueController.processCommand(PlayQueueController::Command::Play, {trackId});
});
entry->bindNew<Wt::WPushButton>("more-btn", Wt::WString::tr("Lms.template.more-btn"), Wt::TextFormat::XHTML);
entry->bindNew<Wt::WPushButton>("play-last", Wt::WString::tr("Lms.Explore.play-last"))
->clicked().connect([=, &tracksAction]
->clicked().connect([=, &playQueueController]
{
tracksAction.emit(PlayQueueAction::PlayLast, {trackId});
playQueueController.processCommand(PlayQueueController::Command::PlayOrAddLast, {trackId});
});
{
+6 -2
View File
@@ -23,15 +23,19 @@
#include <Wt/WWidget.h>
#include "services/database/Object.hpp"
#include "PlayQueueAction.hpp"
namespace Database
{
class Track;
}
namespace UserInterface
{
class PlayQueueController;
}
namespace UserInterface::TrackListHelpers
{
std::unique_ptr<Wt::WWidget> createEntry(const Database::ObjectPtr<Database::Track>& track, PlayQueueActionTrackSignal& tracksAction);
std::unique_ptr<Wt::WWidget> createEntry(const Database::ObjectPtr<Database::Track>& track, PlayQueueController& playQueueController);
} // namespace UserInterface
+11 -9
View File
@@ -30,12 +30,13 @@
#include "utils/String.hpp"
#include "common/InfiniteScrollingContainer.hpp"
#include "explore/Filters.hpp"
#include "explore/PlayQueueController.hpp"
#include "explore/TrackListHelpers.hpp"
#include "resource/DownloadResource.hpp"
#include "Filters.hpp"
#include "LmsApplication.hpp"
#include "LmsApplicationException.hpp"
#include "ModalManager.hpp"
#include "TrackListHelpers.hpp"
#include "Utils.hpp"
using namespace Database;
@@ -52,9 +53,10 @@ namespace
namespace UserInterface
{
TrackList::TrackList(Filters& filters)
TrackList::TrackList(Filters& filters, PlayQueueController& playQueueController)
: Template {Wt::WString::tr("Lms.Explore.TrackList.template")}
, _filters {filters}
, _filters {_filters}
, _playQueueController {playQueueController}
{
addFunction("tr", &Wt::WTemplate::Functions::tr);
addFunction("id", &Wt::WTemplate::Functions::id);
@@ -64,7 +66,7 @@ namespace UserInterface
refreshView();
});
_filters.updated().connect([this]
filters.updated().connect([this]
{
refreshView();
});
@@ -120,19 +122,19 @@ namespace UserInterface
bindNew<Wt::WPushButton>("play-btn", Wt::WString::tr("Lms.Explore.play"), Wt::TextFormat::XHTML)
->clicked().connect([=]
{
trackListAction.emit(PlayQueueAction::Play, *trackListId);
_playQueueController.processCommand(PlayQueueController::Command::Play, *trackListId);
});
bindNew<Wt::WPushButton>("play-shuffled", Wt::WString::tr("Lms.Explore.play-shuffled"), Wt::TextFormat::Plain)
->clicked().connect([=]
{
trackListAction.emit(PlayQueueAction::PlayShuffled, *trackListId);
_playQueueController.processCommand(PlayQueueController::Command::PlayShuffled, *trackListId);
});
bindNew<Wt::WPushButton>("play-last", Wt::WString::tr("Lms.Explore.play-last"), Wt::TextFormat::Plain)
->clicked().connect([=]
{
trackListAction.emit(PlayQueueAction::PlayLast, *trackListId);
_playQueueController.processCommand(PlayQueueController::Command::PlayOrAddLast, *trackListId);
});
bindNew<Wt::WPushButton>("download", Wt::WString::tr("Lms.Explore.download"))
@@ -197,7 +199,7 @@ namespace UserInterface
for (const TrackId trackId : trackIds.results)
{
if (const Track::pointer track {Track::find(LmsApp->getDbSession(), trackId)})
_container->add(TrackListHelpers::createEntry(track, tracksAction));
_container->add(TrackListHelpers::createEntry(track, _playQueueController));
}
_container->setHasMore(trackIds.moreResults && _container->getCount() < _maxCount);
+3 -5
View File
@@ -23,20 +23,17 @@
#include "services/database/Types.hpp"
#include "common/Template.hpp"
#include "PlayQueueAction.hpp"
namespace UserInterface
{
class Filters;
class InfiniteScrollingContainer;
class PlayQueueController;
class TrackList : public Template
{
public:
TrackList(Filters& filters);
PlayQueueActionTrackListSignal trackListAction;
PlayQueueActionTrackSignal tracksAction;
TrackList(Filters& filters, PlayQueueController& playQueueController);
Wt::Signal<Database::TrackListId> trackListDeleted;
@@ -48,6 +45,7 @@ namespace UserInterface
static constexpr std::size_t _maxCount {8000};
Filters& _filters;
PlayQueueController& _playQueueController;
Database::TrackListId _trackListId;
InfiniteScrollingContainer* _container {};
};
-3
View File
@@ -27,7 +27,6 @@
#include "services/database/Types.hpp"
#include "common/Template.hpp"
#include "PlayQueueAction.hpp"
namespace Database
{
@@ -44,8 +43,6 @@ namespace UserInterface
public:
TrackLists(Filters& filters);
PlayQueueActionTrackListSignal trackListAction;
void onTrackListDeleted(Database::TrackListId trackListId);
private:
+11 -9
View File
@@ -26,17 +26,19 @@
#include "utils/Logger.hpp"
#include "common/InfiniteScrollingContainer.hpp"
#include "Filters.hpp"
#include "explore/Filters.hpp"
#include "explore/PlayQueueController.hpp"
#include "explore/TrackListHelpers.hpp"
#include "LmsApplication.hpp"
#include "TrackListHelpers.hpp"
using namespace Database;
namespace UserInterface {
Tracks::Tracks(Filters& filters)
: Template {Wt::WString::tr("Lms.Explore.Tracks.template")},
_trackCollector {filters, _defaultMode, _maxCount}
Tracks::Tracks(Filters& filters, PlayQueueController& playQueueController)
: Template {Wt::WString::tr("Lms.Explore.Tracks.template")}
, _playQueueController {playQueueController}
, _trackCollector {filters, _defaultMode, _maxCount}
{
addFunction("tr", &Wt::WTemplate::Functions::tr);
addFunction("id", &Wt::WTemplate::Functions::id);
@@ -69,18 +71,18 @@ _trackCollector {filters, _defaultMode, _maxCount}
bindNew<Wt::WPushButton>("play-btn", Wt::WString::tr("Lms.Explore.play"), Wt::TextFormat::XHTML)
->clicked().connect([=]
{
tracksAction.emit(PlayQueueAction::Play, getAllTracks());
_playQueueController.processCommand(PlayQueueController::Command::Play, getAllTracks());
});
bindNew<Wt::WPushButton>("play-shuffled", Wt::WString::tr("Lms.Explore.play-shuffled"), Wt::TextFormat::Plain)
->clicked().connect([=]
{
tracksAction.emit(PlayQueueAction::PlayShuffled, getAllTracks());
_playQueueController.processCommand(PlayQueueController::Command::PlayShuffled, getAllTracks());
});
bindNew<Wt::WPushButton>("play-last", Wt::WString::tr("Lms.Explore.play-last"), Wt::TextFormat::Plain)
->clicked().connect([=]
{
tracksAction.emit(PlayQueueAction::PlayLast, getAllTracks());
_playQueueController.processCommand(PlayQueueController::Command::PlayOrAddLast, getAllTracks());
});
_container = bindNew<InfiniteScrollingContainer>("tracks");
@@ -122,7 +124,7 @@ Tracks::addSome()
for (const TrackId trackId : trackIds.results)
{
if (const Track::pointer track {Track::find(LmsApp->getDbSession(), trackId)})
_container->add(TrackListHelpers::createEntry(track, tracksAction));
_container->add(TrackListHelpers::createEntry(track, _playQueueController));
}
_container->setHasMore(trackIds.moreResults);
+3 -4
View File
@@ -22,20 +22,18 @@
#include "services/database/Types.hpp"
#include "common/Template.hpp"
#include "PlayQueueAction.hpp"
#include "TrackCollector.hpp"
namespace UserInterface
{
class Filters;
class InfiniteScrollingContainer;
class PlayQueueController;
class Tracks : public Template
{
public:
Tracks(Filters& filters);
PlayQueueActionTrackSignal tracksAction;
Tracks(Filters& filters, PlayQueueController& playQueueController);
private:
void refreshView();
@@ -48,6 +46,7 @@ namespace UserInterface
static constexpr std::size_t _batchSize {6};
static constexpr std::size_t _maxCount {8000};
PlayQueueController& _playQueueController;
Wt::WWidget* _currentActiveItem {};
InfiniteScrollingContainer* _container {};
TrackCollector _trackCollector;