Adding basic radio mode

This commit is contained in:
emeric
2018-05-30 13:35:49 +02:00
parent b92717fa4f
commit e0ae294abf
8 changed files with 117 additions and 9 deletions
+2 -1
View File
@@ -68,10 +68,11 @@
<message id="Lms.Explore.various-artists">Various artists</message> <message id="Lms.Explore.various-artists">Various artists</message>
<!--Playque--> <!--Playque-->
<message id="Lms.PlayQueue.playqueue">Play Queue</message>
<message id="Lms.PlayQueue.nb-tracks">{1} tracks</message> <message id="Lms.PlayQueue.nb-tracks">{1} tracks</message>
<message id="Lms.PlayQueue.nb-tracks-added">Added {1} tracks</message> <message id="Lms.PlayQueue.nb-tracks-added">Added {1} tracks</message>
<message id="Lms.PlayQueue.nb-tracks-playing">Playing {1} tracks</message> <message id="Lms.PlayQueue.nb-tracks-playing">Playing {1} tracks</message>
<message id="Lms.PlayQueue.playqueue">Play Queue</message>
<message id="Lms.PlayQueue.radio-mode">Radio mode</message>
<!--Settings--> <!--Settings-->
<message id="Lms.Settings.account">Account</message> <message id="Lms.Settings.account">Account</message>
+1
View File
@@ -11,6 +11,7 @@
<h2>${tr:Lms.PlayQueue.playqueue}</h2> <h2>${tr:Lms.PlayQueue.playqueue}</h2>
<small>${nb-tracks}</small> <small>${nb-tracks}</small>
${clear-btn} ${clear-btn}
${radio-mode}
</div> </div>
${entries} ${entries}
${show-more} ${show-more}
+18 -5
View File
@@ -57,6 +57,24 @@ Cluster::getById(Wt::Dbo::Session& session, IdType id)
return session.find<Cluster>().where("id = ?").bind(id); return session.find<Cluster>().where("id = ?").bind(id);
} }
void
Cluster::addTrack(Wt::Dbo::ptr<Track> track)
{
_tracks.insert(track);
}
std::vector<Wt::Dbo::ptr<Track>>
Cluster::getTracks(int offset, int limit) const
{
Wt::Dbo::collection<Track::pointer> res = session()->query<Track::pointer>("select t from track t INNER JOIN cluster c ON c.id = t_c.cluster_id INNER JOIN track_cluster t_c ON t_c.track_id = t.id")
.where("c.id = ?").bind(self()->id())
.offset(offset)
.limit(limit);
return std::vector<Wt::Dbo::ptr<Track>>(res.begin(), res.end());
}
ClusterType::ClusterType(std::string name) ClusterType::ClusterType(std::string name)
: _name(name) : _name(name)
{ {
@@ -117,11 +135,6 @@ ClusterType::getClusters() const
return std::vector<Cluster::pointer>(res.begin(), res.end()); return std::vector<Cluster::pointer>(res.begin(), res.end());
} }
void
Cluster::addTrack(Wt::Dbo::ptr<Track> track)
{
_tracks.insert(track);
}
} // namespace Database } // namespace Database
+2
View File
@@ -53,6 +53,8 @@ class Cluster : public Wt::Dbo::Dbo<Cluster>
const std::string& getName(void) const { return _name; } const std::string& getName(void) const { return _name; }
Wt::Dbo::ptr<ClusterType> getType() const { return _clusterType; } Wt::Dbo::ptr<ClusterType> getType() const { return _clusterType; }
const Wt::Dbo::collection<Wt::Dbo::ptr<Track>>& getTracks() const { return _tracks; } const Wt::Dbo::collection<Wt::Dbo::ptr<Track>>& getTracks() const { return _tracks; }
std::size_t getTrackCount() const { return _tracks.size(); }
std::vector<Wt::Dbo::ptr<Track>> getTracks(int offset, int limit) const;
void addTrack(Wt::Dbo::ptr<Track> track); void addTrack(Wt::Dbo::ptr<Track> track);
+29 -1
View File
@@ -20,6 +20,7 @@
#include <cassert> #include <cassert>
#include "Cluster.hpp"
#include "User.hpp" #include "User.hpp"
#include "Track.hpp" #include "Track.hpp"
@@ -50,7 +51,7 @@ PlaylistEntry::PlaylistEntry()
} }
PlaylistEntry::pointer PlaylistEntry::pointer
PlaylistEntry::getById(Wt::Dbo::Session& session, PlaylistEntry::id_type id) PlaylistEntry::getById(Wt::Dbo::Session& session, IdType id)
{ {
return session.find<PlaylistEntry>().where("id = ?").bind(id); return session.find<PlaylistEntry>().where("id = ?").bind(id);
} }
@@ -133,4 +134,31 @@ Playlist::getCount() const
return _entries.size(); return _entries.size();
} }
std::vector<Wt::Dbo::ptr<Cluster>>
Playlist::getClusters() const
{
assert(session());
assert(IdIsValid(self()->id()));
Wt::Dbo::collection<Cluster::pointer> res = session()->query<Cluster::pointer>("SELECT c from cluster c INNER JOIN track t ON c.id = t_c.cluster_id INNER JOIN track_cluster t_c ON t_c.track_id = t.id INNER JOIN playlist_entry p_e ON p_e.track_id = t.id INNER JOIN playlist p ON p.id = p_e.playlist_id")
.where("p.id = ?").bind(self()->id())
.groupBy("c.id")
.orderBy("COUNT(c.id) DESC");
return std::vector<Wt::Dbo::ptr<Cluster>>(res.begin(), res.end());
}
bool
Playlist::hasTrack(IdType trackId) const
{
assert(session());
assert(IdIsValid(self()->id()));
Wt::Dbo::collection<PlaylistEntry::pointer> res = session()->query<PlaylistEntry::pointer>("SELECT p_e from playlist_entry p_e INNER JOIN playlist p ON p_e.playlist_id = p.id")
.where("p_e.track_id = ?").bind(trackId);
return res.size() > 0;
}
} // namespace Database } // namespace Database
+9 -2
View File
@@ -23,11 +23,14 @@
#include <string> #include <string>
#include "Types.hpp"
namespace Database { namespace Database {
class PlaylistEntry; class PlaylistEntry;
class User; class User;
class Track; class Track;
class Cluster;
class Playlist : public Wt::Dbo::Dbo<Playlist> class Playlist : public Wt::Dbo::Dbo<Playlist>
{ {
@@ -56,6 +59,11 @@ class Playlist : public Wt::Dbo::Dbo<Playlist>
Wt::Dbo::ptr<PlaylistEntry> getEntry(std::size_t pos) const; Wt::Dbo::ptr<PlaylistEntry> getEntry(std::size_t pos) const;
std::vector<Wt::Dbo::ptr<PlaylistEntry>> getEntries(int offset, int size, bool& moreResults) const; std::vector<Wt::Dbo::ptr<PlaylistEntry>> getEntries(int offset, int size, bool& moreResults) const;
// Get clusters, order by occurence
std::vector<Wt::Dbo::ptr<Cluster>> getClusters() const;
bool hasTrack(IdType trackId) const;
template<class Action> template<class Action>
void persist(Action& a) void persist(Action& a)
{ {
@@ -79,12 +87,11 @@ class PlaylistEntry
public: public:
using pointer = Wt::Dbo::ptr<PlaylistEntry>; using pointer = Wt::Dbo::ptr<PlaylistEntry>;
using id_type = Wt::Dbo::dbo_traits<PlaylistEntry>::IdType;
PlaylistEntry(); PlaylistEntry();
PlaylistEntry(Wt::Dbo::ptr<Track> track, Wt::Dbo::ptr<Playlist> playlist); PlaylistEntry(Wt::Dbo::ptr<Track> track, Wt::Dbo::ptr<Playlist> playlist);
static pointer getById(Wt::Dbo::Session& session, id_type id); static pointer getById(Wt::Dbo::Session& session, IdType id);
// Create utility // Create utility
static pointer create(Wt::Dbo::Session& session, Wt::Dbo::ptr<Track> track, Wt::Dbo::ptr<Playlist> playlist); static pointer create(Wt::Dbo::Session& session, Wt::Dbo::ptr<Track> track, Wt::Dbo::ptr<Playlist> playlist);
+52
View File
@@ -19,6 +19,8 @@
#include "PlayQueueView.hpp" #include "PlayQueueView.hpp"
#include <random>
#include <Wt/WAnchor.h> #include <Wt/WAnchor.h>
#include <Wt/WImage.h> #include <Wt/WImage.h>
#include <Wt/WText.h> #include <Wt/WText.h>
@@ -46,6 +48,8 @@ PlayQueue::PlayQueue()
_showMore->addFunction("tr", &Wt::WTemplate::Functions::tr); _showMore->addFunction("tr", &Wt::WTemplate::Functions::tr);
_showMore->setHidden(true); _showMore->setHidden(true);
_radioMode = bindNew<Wt::WCheckBox>("radio-mode", Wt::WString::tr("Lms.PlayQueue.radio-mode"));
_nbTracks = bindNew<Wt::WText>("nb-tracks"); _nbTracks = bindNew<Wt::WText>("nb-tracks");
{ {
@@ -107,6 +111,10 @@ PlayQueue::play(std::size_t pos)
return; return;
} }
// If last and radio mode, fill the next song
if (_radioMode->checkState() == Wt::CheckState::Checked && pos == playlist->getCount() - 1)
addRadioTrack();
_trackPos = pos; _trackPos = pos;
trackId = playlist->getEntry(*_trackPos)->getTrack().id(); trackId = playlist->getEntry(*_trackPos)->getTrack().id();
updateCurrentTrack(true); updateCurrentTrack(true);
@@ -181,6 +189,12 @@ PlayQueue::enqueueTracks(const std::vector<Database::Track::pointer>& tracks)
addSome(); addSome();
} }
void
PlayQueue::enqueueTrack(Database::Track::pointer track)
{
enqueueTracks(std::vector<Database::Track::pointer>(1, track));
}
void void
PlayQueue::addTracks(const std::vector<Database::Track::pointer>& tracks) PlayQueue::addTracks(const std::vector<Database::Track::pointer>& tracks)
{ {
@@ -274,5 +288,43 @@ PlayQueue::addSome()
_showMore->setHidden(!moreResults); _showMore->setHidden(!moreResults);
} }
void
PlayQueue::addRadioTrack()
{
auto now = std::chrono::system_clock::now();
std::mt19937 randGenerator(std::chrono::duration_cast<std::chrono::milliseconds>(now.time_since_epoch()).count());
LMS_LOG(UI, INFO) << "Radio mode: adding track";
auto playlist = Database::Playlist::get(LmsApp->getDboSession(), currentPlayQueueName, LmsApp->getCurrentUser());
// Get all the tracks of the playlist, get the cluster that is mostly used
// and reuse it to get the next track
auto clusters = playlist->getClusters();
if (clusters.empty())
return;
for (auto cluster : clusters)
{
LMS_LOG(UI, DEBUG) << "Processing cluster '" << cluster->getName() << "'";
auto nbTracks = cluster->getTrackCount();
if (nbTracks == 0)
continue;
std::uniform_int_distribution<int> dist(0, nbTracks - 1);
auto trackToAdd = cluster->getTracks(dist(randGenerator), 1).front();
if (!playlist->hasTrack(trackToAdd.id()))
{
enqueueTrack(trackToAdd);
return;
}
}
LMS_LOG(UI, INFO) << "No more track to be added!";
}
} // namespace UserInterface } // namespace UserInterface
+4
View File
@@ -19,6 +19,7 @@
#pragma once #pragma once
#include <Wt/WCheckBox.h>
#include <Wt/WContainerWidget.h> #include <Wt/WContainerWidget.h>
#include <Wt/WSignal.h> #include <Wt/WSignal.h>
#include <Wt/WTemplate.h> #include <Wt/WTemplate.h>
@@ -52,13 +53,16 @@ class PlayQueue : public Wt::WTemplate
private: private:
void enqueueTracks(const std::vector<Database::Track::pointer>& tracks); void enqueueTracks(const std::vector<Database::Track::pointer>& tracks);
void enqueueTrack(Database::Track::pointer track);
void addSome(); void addSome();
void addRadioTrack();
void updateInfo(); void updateInfo();
void updateCurrentTrack(bool selected); void updateCurrentTrack(bool selected);
void play(std::size_t pos); void play(std::size_t pos);
void stop(); void stop();
Wt::WCheckBox* _radioMode;
Wt::WContainerWidget* _entriesContainer; Wt::WContainerWidget* _entriesContainer;
Wt::WTemplate* _showMore; Wt::WTemplate* _showMore;
Wt::WText* _nbTracks; Wt::WText* _nbTracks;