Added a way to play tracks per disc, fixes #423
This commit is contained in:
+15
-1
@@ -50,7 +50,21 @@
|
|||||||
</message>
|
</message>
|
||||||
|
|
||||||
<message id="Lms.Explore.Release.template.entry-disc">
|
<message id="Lms.Explore.Release.template.entry-disc">
|
||||||
<h4>${disc-title}</h4>
|
<div class="d-flex">
|
||||||
|
<h4 class="flex-fill">${disc-title}</h4>
|
||||||
|
<div class="p-2 d-flex">
|
||||||
|
${play-btn class="d-none d-sm-block btn btn-sm btn-outline-secondary border-0"}
|
||||||
|
<div class="dropdown d-inline-block">
|
||||||
|
${more-btn data-bs-toggle="dropdown" aria-expanded="false" class="btn btn-sm btn-outline-secondary border-0"}
|
||||||
|
<ul class="dropdown-menu" aria-labelledby="${id:more-btn}">
|
||||||
|
<li class="d-block d-sm-none">${play class="dropdown-item"}</li>
|
||||||
|
<li>${play-next class="dropdown-item"}</li>
|
||||||
|
<li>${play-last class="dropdown-item"}</li>
|
||||||
|
<li>${play-shuffled class="dropdown-item"}</li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
${tracks class="d-grid gap-1 Lms-row-container"}
|
${tracks class="d-grid gap-1 Lms-row-container"}
|
||||||
</message>
|
</message>
|
||||||
|
|
||||||
|
|||||||
@@ -142,6 +142,9 @@ namespace Database
|
|||||||
if (params.trackNumber)
|
if (params.trackNumber)
|
||||||
query.where("t.track_number = ?").bind(*params.trackNumber);
|
query.where("t.track_number = ?").bind(*params.trackNumber);
|
||||||
|
|
||||||
|
if (params.discNumber)
|
||||||
|
query.where("t.disc_number = ?").bind(*params.discNumber);
|
||||||
|
|
||||||
if (params.mediaLibrary.isValid())
|
if (params.mediaLibrary.isValid())
|
||||||
query.where("t.media_library_id = ?").bind(params.mediaLibrary);
|
query.where("t.media_library_id = ?").bind(params.mediaLibrary);
|
||||||
|
|
||||||
|
|||||||
@@ -78,6 +78,7 @@ namespace Database {
|
|||||||
std::string releaseName; // matching this release name
|
std::string releaseName; // matching this release name
|
||||||
TrackListId trackList; // matching this trackList
|
TrackListId trackList; // matching this trackList
|
||||||
std::optional<int> trackNumber; // matching this track number
|
std::optional<int> trackNumber; // matching this track number
|
||||||
|
std::optional<int> discNumber; // matching this disc number
|
||||||
MediaLibraryId mediaLibrary; // If set, tracks in this library
|
MediaLibraryId mediaLibrary; // If set, tracks in this library
|
||||||
|
|
||||||
FindParameters& setClusters(const std::vector<ClusterId>& _clusters) { clusters = _clusters; return *this; }
|
FindParameters& setClusters(const std::vector<ClusterId>& _clusters) { clusters = _clusters; return *this; }
|
||||||
@@ -94,6 +95,7 @@ namespace Database {
|
|||||||
FindParameters& setReleaseName(std::string_view _releaseName) { releaseName = _releaseName; return *this; }
|
FindParameters& setReleaseName(std::string_view _releaseName) { releaseName = _releaseName; return *this; }
|
||||||
FindParameters& setTrackList(TrackListId _trackList) { trackList = _trackList; return *this; }
|
FindParameters& setTrackList(TrackListId _trackList) { trackList = _trackList; return *this; }
|
||||||
FindParameters& setTrackNumber(int _trackNumber) { trackNumber = _trackNumber; return *this; }
|
FindParameters& setTrackNumber(int _trackNumber) { trackNumber = _trackNumber; return *this; }
|
||||||
|
FindParameters& setDiscNumber(int _discNumber) { discNumber = _discNumber; return *this; }
|
||||||
FindParameters& setMediaLibrary(MediaLibraryId _mediaLibrary) { mediaLibrary = _mediaLibrary; return *this; }
|
FindParameters& setMediaLibrary(MediaLibraryId _mediaLibrary) { mediaLibrary = _mediaLibrary; return *this; }
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -88,6 +88,36 @@ namespace UserInterface
|
|||||||
return res;
|
return res;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
std::vector<Database::TrackId> getDiscTracks(Database::Session& session, const std::vector<PlayQueueController::Disc>& discs, const std::vector<Database::ClusterId>& clusters, std::size_t maxTrackCount)
|
||||||
|
{
|
||||||
|
using namespace Database;
|
||||||
|
assert(maxTrackCount);
|
||||||
|
|
||||||
|
std::vector<TrackId> res;
|
||||||
|
|
||||||
|
auto transaction{ session.createReadTransaction() };
|
||||||
|
|
||||||
|
for (const PlayQueueController::Disc& disc : discs)
|
||||||
|
{
|
||||||
|
Database::Track::FindParameters params;
|
||||||
|
params.setRelease(disc.releaseId);
|
||||||
|
params.setSortMethod(Database::TrackSortMethod::Release);
|
||||||
|
params.setDiscNumber(disc.discNumber);
|
||||||
|
params.setClusters(clusters);
|
||||||
|
params.setRange(Database::Range{ 0, maxTrackCount - res.size() });
|
||||||
|
|
||||||
|
const auto tracks{ Database::Track::findIds(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;
|
||||||
|
}
|
||||||
|
|
||||||
std::vector<Database::TrackId> getTrackListTracks(Database::Session& session, Database::TrackListId trackListId, const std::vector<Database::ClusterId>& clusters, std::size_t maxTrackCount)
|
std::vector<Database::TrackId> getTrackListTracks(Database::Session& session, Database::TrackListId trackListId, const std::vector<Database::ClusterId>& clusters, std::size_t maxTrackCount)
|
||||||
{
|
{
|
||||||
using namespace Database;
|
using namespace Database;
|
||||||
@@ -149,6 +179,12 @@ namespace UserInterface
|
|||||||
processCommand(command, tracks);
|
processCommand(command, tracks);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void PlayQueueController::processCommand(Command command, const std::vector<Disc>& discs)
|
||||||
|
{
|
||||||
|
const std::vector<Database::TrackId> tracks{ getDiscTracks(LmsApp->getDbSession(), discs, _filters.getClusterIds(), _maxTrackCountToEnqueue) };
|
||||||
|
processCommand(command, tracks);
|
||||||
|
}
|
||||||
|
|
||||||
void PlayQueueController::playTrackInRelease(Database::TrackId trackId)
|
void PlayQueueController::playTrackInRelease(Database::TrackId trackId)
|
||||||
{
|
{
|
||||||
Database::ReleaseId releaseId;
|
Database::ReleaseId releaseId;
|
||||||
|
|||||||
@@ -45,11 +45,18 @@ namespace UserInterface
|
|||||||
PlayShuffled,
|
PlayShuffled,
|
||||||
};
|
};
|
||||||
|
|
||||||
void processCommand(Command command, const std::vector<Database::ArtistId>&);
|
void processCommand(Command command, const std::vector<Database::ArtistId>& artists);
|
||||||
void processCommand(Command command, const std::vector<Database::ReleaseId>&);
|
void processCommand(Command command, const std::vector<Database::ReleaseId>& releases);
|
||||||
void processCommand(Command command, const std::vector<Database::TrackId>&);
|
void processCommand(Command command, const std::vector<Database::TrackId>& tracks);
|
||||||
void processCommand(Command command, Database::TrackListId);
|
|
||||||
void playTrackInRelease(Database::TrackId);
|
struct Disc
|
||||||
|
{
|
||||||
|
Database::ReleaseId releaseId;
|
||||||
|
size_t discNumber;
|
||||||
|
};
|
||||||
|
void processCommand(Command command, const std::vector<Disc>& discs);
|
||||||
|
void processCommand(Command command, Database::TrackListId trackList);
|
||||||
|
void playTrackInRelease(Database::TrackId track);
|
||||||
|
|
||||||
void setMaxTrackCountToEnqueue(std::size_t maxTrackCount) { _maxTrackCountToEnqueue = maxTrackCount; }
|
void setMaxTrackCountToEnqueue(std::size_t maxTrackCount) { _maxTrackCountToEnqueue = maxTrackCount; }
|
||||||
|
|
||||||
|
|||||||
@@ -340,21 +340,47 @@ namespace UserInterface
|
|||||||
|
|
||||||
// Expect to be called in asc order
|
// Expect to be called in asc order
|
||||||
std::map<std::size_t, Wt::WContainerWidget*> trackContainers;
|
std::map<std::size_t, Wt::WContainerWidget*> trackContainers;
|
||||||
auto getOrAddDiscContainer = [&](std::size_t discNumber, const std::string& discSubtitle) -> Wt::WContainerWidget*
|
auto getOrAddDiscContainer = [&, releaseId = _releaseId](std::size_t discNumber, const std::string& discSubtitle) -> Wt::WContainerWidget*
|
||||||
{
|
{
|
||||||
{
|
if (auto it{ trackContainers.find(discNumber) }; it != std::cend(trackContainers))
|
||||||
auto it = trackContainers.find(discNumber);
|
return it->second;
|
||||||
if (it != std::cend(trackContainers))
|
|
||||||
return it->second;
|
|
||||||
}
|
|
||||||
|
|
||||||
Wt::WTemplate* disc{ rootContainer->addNew<Wt::WTemplate>(Wt::WString::tr("Lms.Explore.Release.template.entry-disc")) };
|
Template* disc{ rootContainer->addNew<Template>(Wt::WString::tr("Lms.Explore.Release.template.entry-disc")) };
|
||||||
|
disc->addFunction("id", &Wt::WTemplate::Functions::id);
|
||||||
|
|
||||||
if (discSubtitle.empty())
|
if (discSubtitle.empty())
|
||||||
disc->bindNew<Wt::WText>("disc-title", Wt::WString::tr("Lms.Explore.Release.disc").arg(discNumber));
|
disc->bindNew<Wt::WText>("disc-title", Wt::WString::tr("Lms.Explore.Release.disc").arg(discNumber));
|
||||||
else
|
else
|
||||||
disc->bindString("disc-title", Wt::WString::fromUTF8(discSubtitle), Wt::TextFormat::Plain);
|
disc->bindString("disc-title", Wt::WString::fromUTF8(discSubtitle), Wt::TextFormat::Plain);
|
||||||
|
|
||||||
|
Wt::WPushButton* playBtn{ disc->bindNew<Wt::WPushButton>("play-btn", Wt::WString::tr("Lms.template.play-btn"), Wt::TextFormat::XHTML) };
|
||||||
|
playBtn->clicked().connect([this, releaseId, discNumber]
|
||||||
|
{
|
||||||
|
_playQueueController.processCommand(PlayQueueController::Command::Play, { PlayQueueController::Disc{ releaseId, discNumber} });
|
||||||
|
});
|
||||||
|
disc->bindNew<Wt::WPushButton>("more-btn", Wt::WString::tr("Lms.template.more-btn"), Wt::TextFormat::XHTML);
|
||||||
|
disc->bindNew<Wt::WPushButton>("play", Wt::WString::tr("Lms.Explore.play"))
|
||||||
|
->clicked().connect([this, releaseId, discNumber]
|
||||||
|
{
|
||||||
|
_playQueueController.processCommand(PlayQueueController::Command::Play, { PlayQueueController::Disc{ releaseId, discNumber} });
|
||||||
|
});
|
||||||
|
disc->bindNew<Wt::WPushButton>("play-next", Wt::WString::tr("Lms.Explore.play-next"))
|
||||||
|
->clicked().connect([this, releaseId, discNumber]
|
||||||
|
{
|
||||||
|
_playQueueController.processCommand(PlayQueueController::Command::PlayNext, { PlayQueueController::Disc{ releaseId, discNumber } });
|
||||||
|
});
|
||||||
|
|
||||||
|
disc->bindNew<Wt::WPushButton>("play-shuffled", Wt::WString::tr("Lms.Explore.play-shuffled"), Wt::TextFormat::Plain)
|
||||||
|
->clicked().connect([this, releaseId, discNumber]
|
||||||
|
{
|
||||||
|
_playQueueController.processCommand(PlayQueueController::Command::PlayShuffled, { PlayQueueController::Disc{ releaseId, discNumber } });
|
||||||
|
});
|
||||||
|
disc->bindNew<Wt::WPushButton>("play-last", Wt::WString::tr("Lms.Explore.play-last"))
|
||||||
|
->clicked().connect([this, releaseId, discNumber]
|
||||||
|
{
|
||||||
|
_playQueueController.processCommand(PlayQueueController::Command::PlayOrAddLast, { PlayQueueController::Disc{ releaseId, discNumber } });
|
||||||
|
});
|
||||||
|
|
||||||
Wt::WContainerWidget* tracksContainer{ disc->bindNew<Wt::WContainerWidget>("tracks") };
|
Wt::WContainerWidget* tracksContainer{ disc->bindNew<Wt::WContainerWidget>("tracks") };
|
||||||
trackContainers[discNumber] = tracksContainer;
|
trackContainers[discNumber] = tracksContainer;
|
||||||
|
|
||||||
@@ -390,6 +416,7 @@ namespace UserInterface
|
|||||||
container = getOrAddNoDiscContainer();
|
container = getOrAddNoDiscContainer();
|
||||||
|
|
||||||
Template* entry{ container->addNew<Template>(Wt::WString::tr("Lms.Explore.Release.template.entry")) };
|
Template* entry{ container->addNew<Template>(Wt::WString::tr("Lms.Explore.Release.template.entry")) };
|
||||||
|
entry->addFunction("id", &Wt::WTemplate::Functions::id);
|
||||||
|
|
||||||
entry->bindString("name", Wt::WString::fromUTF8(track->getName()), Wt::TextFormat::Plain);
|
entry->bindString("name", Wt::WString::fromUTF8(track->getName()), Wt::TextFormat::Plain);
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user