Added some logic in the playqueue (play next / play previous handling)
This commit is contained in:
+1
-187
@@ -1,191 +1,5 @@
|
|||||||
.main-nav {
|
|
||||||
margin-bottom: 0px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.playqueue {
|
.Lms-playqueue-selected {
|
||||||
background-color: #EEE;
|
|
||||||
border-radius: 10px;
|
|
||||||
box-shadow: 0px 2px 3px rgba(0, 0, 0, 0.4);
|
|
||||||
}
|
|
||||||
|
|
||||||
.playqueue-playing {
|
|
||||||
font-weight: bold;
|
font-weight: bold;
|
||||||
}
|
}
|
||||||
|
|
||||||
.playqueue-track {
|
|
||||||
font-size: 120%;
|
|
||||||
margin-top: 12px;
|
|
||||||
line-height: normal;
|
|
||||||
}
|
|
||||||
|
|
||||||
.playqueue-artist {
|
|
||||||
line-height: normal;
|
|
||||||
font-style: italic;
|
|
||||||
}
|
|
||||||
|
|
||||||
.playqueue-cover {
|
|
||||||
width: 64px;
|
|
||||||
height: 64px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.mediaplayer-track {
|
|
||||||
font-weight: bold;
|
|
||||||
font-size: 120%;
|
|
||||||
}
|
|
||||||
|
|
||||||
.mediaplayer-artist {
|
|
||||||
font-style: italic;
|
|
||||||
}
|
|
||||||
|
|
||||||
.mediaplayer {
|
|
||||||
background-color: #CCC;
|
|
||||||
border-radius: 10px;
|
|
||||||
display: flex;
|
|
||||||
justify-content: space-between;
|
|
||||||
align-items: center;
|
|
||||||
height: 72px;
|
|
||||||
box-shadow: 0px 2px 3px rgba(0, 0, 0, 0.4);
|
|
||||||
}
|
|
||||||
|
|
||||||
.mediaplayer-cover {
|
|
||||||
margin: 8px;
|
|
||||||
width: 64px;
|
|
||||||
min-width: 64px;
|
|
||||||
height: 64px;
|
|
||||||
min-height: 64px;
|
|
||||||
display: flex;
|
|
||||||
justify-content: space-between;
|
|
||||||
align-items: center;
|
|
||||||
}
|
|
||||||
|
|
||||||
.mediaplayer-cover img {
|
|
||||||
border-radius: 8px;
|
|
||||||
border-radius: 8px;
|
|
||||||
box-shadow: 0px 2px 3px rgba(0, 0, 0, 0.4);
|
|
||||||
}
|
|
||||||
|
|
||||||
.mediaplayer-btn {
|
|
||||||
margin: 8px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.mediaplayer-btn i {
|
|
||||||
cursor: pointer;
|
|
||||||
text-shadow: 0px 2px 3px rgba(0, 0, 0, 0.4);
|
|
||||||
}
|
|
||||||
|
|
||||||
.mediaplayer-btn-active {
|
|
||||||
color: #428bca;
|
|
||||||
}
|
|
||||||
|
|
||||||
.mediaplayer-info-container {
|
|
||||||
width: 100%;
|
|
||||||
min-width: 100px;
|
|
||||||
margin: 8px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.mediaplayer-track-info {
|
|
||||||
text-overflow: ellipsis;
|
|
||||||
overflow: hidden;
|
|
||||||
white-space: nowrap;
|
|
||||||
}
|
|
||||||
|
|
||||||
.mediaplayer-progress-container {
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
}
|
|
||||||
|
|
||||||
.mediaplayer-badge {
|
|
||||||
min-width: initial;
|
|
||||||
}
|
|
||||||
|
|
||||||
.mediaplayer-current-duration {
|
|
||||||
margin-right: 8px;
|
|
||||||
}
|
|
||||||
.mediaplayer-total-duration {
|
|
||||||
margin-left: 8px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.mediaplayer-seekbar {
|
|
||||||
}
|
|
||||||
|
|
||||||
.mediaplayer-volume-container {
|
|
||||||
width: 24px;
|
|
||||||
height: 64px;
|
|
||||||
margin: 8px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.mediaplayer-volume {
|
|
||||||
height: 64px;
|
|
||||||
-webkit-appearance: slider-vertical;
|
|
||||||
writing-mode: bt-lr;
|
|
||||||
}
|
|
||||||
|
|
||||||
.mobile-btn {
|
|
||||||
margin: 8px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.mobile-btn-active {
|
|
||||||
color: #428bca;
|
|
||||||
}
|
|
||||||
|
|
||||||
.mobile-btn i {
|
|
||||||
cursor: pointer;
|
|
||||||
text-shadow: 0px 2px 3px rgba(0, 0, 0, 0.4);
|
|
||||||
}
|
|
||||||
|
|
||||||
.mobile-search-title {
|
|
||||||
font-weight: bold;
|
|
||||||
height: 32px;
|
|
||||||
line-height: 32px;
|
|
||||||
background-color: grey;
|
|
||||||
color: white;
|
|
||||||
text-align: center;
|
|
||||||
}
|
|
||||||
|
|
||||||
.mobile-search-entry {
|
|
||||||
min-height: 64px;
|
|
||||||
border-bottom: 1px solid lightgray;
|
|
||||||
overflow: hidden;
|
|
||||||
text-overflow: ellipsis;
|
|
||||||
}
|
|
||||||
|
|
||||||
.mobile-search-entry:active {
|
|
||||||
background-color: lightgrey;
|
|
||||||
}
|
|
||||||
|
|
||||||
.mobile-search-more {
|
|
||||||
height: 64px;
|
|
||||||
border-bottom: 1px solid lightgray;
|
|
||||||
}
|
|
||||||
|
|
||||||
.mobile-search-more:active {
|
|
||||||
background-color: lightgrey;
|
|
||||||
}
|
|
||||||
|
|
||||||
.mobile-track {
|
|
||||||
font-weight: bold;
|
|
||||||
}
|
|
||||||
|
|
||||||
.mobile-artist {
|
|
||||||
font-style: italic;
|
|
||||||
}
|
|
||||||
|
|
||||||
.vertical-align {
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
}
|
|
||||||
|
|
||||||
.mobile-audio-footer {
|
|
||||||
background-color: #f5f5f5;
|
|
||||||
height: 60px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.mobile-audio-player-cover {
|
|
||||||
width: 48px;
|
|
||||||
height: 48px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.release_res_shadow {
|
|
||||||
box-shadow: 0px 2px 3px rgba(0, 0, 0, 0.5)
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|||||||
@@ -109,6 +109,19 @@ Playlist::getEntries(int offset, int size, bool& moreResults) const
|
|||||||
return res;
|
return res;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Wt::Dbo::ptr<PlaylistEntry>
|
||||||
|
Playlist::getEntry(std::size_t pos) const
|
||||||
|
{
|
||||||
|
Wt::Dbo::ptr<PlaylistEntry> res;
|
||||||
|
|
||||||
|
bool moreResults;
|
||||||
|
auto entries = getEntries(pos, 1, moreResults);
|
||||||
|
if (!entries.empty())
|
||||||
|
res = entries.front();
|
||||||
|
|
||||||
|
return res;
|
||||||
|
}
|
||||||
|
|
||||||
std::size_t
|
std::size_t
|
||||||
Playlist::getCount() const
|
Playlist::getCount() const
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -54,6 +54,7 @@ class Playlist : public Wt::Dbo::Dbo<Playlist>
|
|||||||
|
|
||||||
// Get tracks, ordered by position
|
// Get tracks, ordered by position
|
||||||
std::size_t getCount() const;
|
std::size_t getCount() 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;
|
||||||
|
|
||||||
template<class Action>
|
template<class Action>
|
||||||
|
|||||||
@@ -340,6 +340,23 @@ LmsApplication::handleAuthEvent(void)
|
|||||||
auto player = new MediaPlayer();
|
auto player = new MediaPlayer();
|
||||||
main->bindWidget("player", player);
|
main->bindWidget("player", player);
|
||||||
|
|
||||||
|
// Events from MediaPlayer
|
||||||
|
player->playNext.connect(std::bind([=]
|
||||||
|
{
|
||||||
|
playqueue->playNext();
|
||||||
|
}));
|
||||||
|
player->playPrevious.connect(std::bind([=]
|
||||||
|
{
|
||||||
|
playqueue->playPrevious();
|
||||||
|
}));
|
||||||
|
player->playbackEnded.connect(std::bind([=]
|
||||||
|
{
|
||||||
|
playqueue->playNext();
|
||||||
|
}));
|
||||||
|
|
||||||
|
// Events from the PlayQueue
|
||||||
|
playqueue->playTrack.connect(player, &MediaPlayer::playTrack);
|
||||||
|
|
||||||
// Events from MediaScanner
|
// Events from MediaScanner
|
||||||
if (CurrentUser()->isAdmin())
|
if (CurrentUser()->isAdmin())
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -21,6 +21,8 @@
|
|||||||
#include <Wt/WTemplate>
|
#include <Wt/WTemplate>
|
||||||
#include <Wt/WText>
|
#include <Wt/WText>
|
||||||
|
|
||||||
|
#include "utils/Logger.hpp"
|
||||||
|
|
||||||
#include "MediaPlayer.hpp"
|
#include "MediaPlayer.hpp"
|
||||||
|
|
||||||
namespace UserInterface {
|
namespace UserInterface {
|
||||||
@@ -35,9 +37,17 @@ MediaPlayer::MediaPlayer(Wt::WContainerWidget* parent)
|
|||||||
|
|
||||||
auto prevBtn = new Wt::WText(Wt::WString::tr("btn-mediaplayer-prev"), Wt::XHTMLText);
|
auto prevBtn = new Wt::WText(Wt::WString::tr("btn-mediaplayer-prev"), Wt::XHTMLText);
|
||||||
player->bindWidget("previous", prevBtn);
|
player->bindWidget("previous", prevBtn);
|
||||||
|
prevBtn->clicked().connect(std::bind([=]
|
||||||
|
{
|
||||||
|
playPrevious.emit();
|
||||||
|
}));
|
||||||
|
|
||||||
auto nextBtn = new Wt::WText(Wt::WString::tr("btn-mediaplayer-next"), Wt::XHTMLText);
|
auto nextBtn = new Wt::WText(Wt::WString::tr("btn-mediaplayer-next"), Wt::XHTMLText);
|
||||||
player->bindWidget("next", nextBtn);
|
player->bindWidget("next", nextBtn);
|
||||||
|
nextBtn->clicked().connect(std::bind([=]
|
||||||
|
{
|
||||||
|
playNext.emit();
|
||||||
|
}));
|
||||||
|
|
||||||
auto shuffleBtn = new Wt::WText(Wt::WString::tr("btn-mediaplayer-shuffle"), Wt::XHTMLText);
|
auto shuffleBtn = new Wt::WText(Wt::WString::tr("btn-mediaplayer-shuffle"), Wt::XHTMLText);
|
||||||
player->bindWidget("shuffle", shuffleBtn);
|
player->bindWidget("shuffle", shuffleBtn);
|
||||||
@@ -53,7 +63,12 @@ MediaPlayer::MediaPlayer(Wt::WContainerWidget* parent)
|
|||||||
|
|
||||||
auto trackName = new Wt::WText("---");
|
auto trackName = new Wt::WText("---");
|
||||||
player->bindWidget("track-name", trackName);
|
player->bindWidget("track-name", trackName);
|
||||||
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
MediaPlayer::playTrack(Database::Track::id_type id)
|
||||||
|
{
|
||||||
|
LMS_LOG(UI, DEBUG) << "Playing track ID = " << id;
|
||||||
}
|
}
|
||||||
|
|
||||||
} // namespace UserInterface
|
} // namespace UserInterface
|
||||||
|
|||||||
@@ -19,14 +19,25 @@
|
|||||||
|
|
||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
|
#include <Wt/WSignal>
|
||||||
#include <Wt/WContainerWidget>
|
#include <Wt/WContainerWidget>
|
||||||
|
|
||||||
|
#include "database/Types.hpp"
|
||||||
|
|
||||||
namespace UserInterface {
|
namespace UserInterface {
|
||||||
|
|
||||||
class MediaPlayer : public Wt::WContainerWidget
|
class MediaPlayer : public Wt::WContainerWidget
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
MediaPlayer(Wt::WContainerWidget* parent = 0);
|
MediaPlayer(Wt::WContainerWidget* parent = 0);
|
||||||
|
|
||||||
|
void playTrack(Database::Track::id_type);
|
||||||
|
|
||||||
|
// Signals
|
||||||
|
Wt::Signal<void> playbackEnded;
|
||||||
|
Wt::Signal<void> playPrevious;
|
||||||
|
Wt::Signal<void> playNext;
|
||||||
|
|
||||||
};
|
};
|
||||||
|
|
||||||
} // namespace UserInterface
|
} // namespace UserInterface
|
||||||
|
|||||||
@@ -75,6 +75,8 @@ PlayQueue::PlayQueue(Wt::WContainerWidget* parent)
|
|||||||
auto playlist = Database::Playlist::get(DboSession(), currentPlayQueueName, CurrentUser());
|
auto playlist = Database::Playlist::get(DboSession(), currentPlayQueueName, CurrentUser());
|
||||||
if (!playlist)
|
if (!playlist)
|
||||||
playlist = Database::Playlist::create(DboSession(), currentPlayQueueName, false, CurrentUser());
|
playlist = Database::Playlist::create(DboSession(), currentPlayQueueName, false, CurrentUser());
|
||||||
|
|
||||||
|
_trackPos = CurrentUser()->getCurPlayingTrackPos();
|
||||||
}
|
}
|
||||||
|
|
||||||
_showMore->clicked().connect(std::bind([=]
|
_showMore->clicked().connect(std::bind([=]
|
||||||
@@ -86,6 +88,63 @@ PlayQueue::PlayQueue(Wt::WContainerWidget* parent)
|
|||||||
addSome();
|
addSome();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
PlayQueue::stop()
|
||||||
|
{
|
||||||
|
updateCurrentTrack(false);
|
||||||
|
_trackPos.reset();
|
||||||
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
PlayQueue::play(std::size_t pos)
|
||||||
|
{
|
||||||
|
updateCurrentTrack(false);
|
||||||
|
|
||||||
|
Database::Track::id_type trackId;
|
||||||
|
{
|
||||||
|
Wt::Dbo::Transaction transaction(DboSession());
|
||||||
|
|
||||||
|
auto playlist = Database::Playlist::get(DboSession(), currentPlayQueueName, CurrentUser());
|
||||||
|
|
||||||
|
// If out of range, stop playing
|
||||||
|
if (pos >= playlist->getCount())
|
||||||
|
{
|
||||||
|
stop();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
_trackPos = pos;
|
||||||
|
trackId = playlist->getEntry(*_trackPos)->getTrack().id();
|
||||||
|
updateCurrentTrack(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
playTrack.emit(trackId);
|
||||||
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
PlayQueue::playPrevious()
|
||||||
|
{
|
||||||
|
if (!_trackPos)
|
||||||
|
return;
|
||||||
|
|
||||||
|
if (*_trackPos == 0)
|
||||||
|
stop();
|
||||||
|
else
|
||||||
|
play(*_trackPos - 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
PlayQueue::playNext()
|
||||||
|
{
|
||||||
|
if (!_trackPos)
|
||||||
|
{
|
||||||
|
play(0);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
play(*_trackPos + 1);
|
||||||
|
}
|
||||||
|
|
||||||
void
|
void
|
||||||
PlayQueue::updateInfo()
|
PlayQueue::updateInfo()
|
||||||
{
|
{
|
||||||
@@ -95,6 +154,22 @@ PlayQueue::updateInfo()
|
|||||||
_nbTracks->setText(Wt::WString::tr("Lms.PlayQueue.nb-tracks").arg(playlist->getCount()));
|
_nbTracks->setText(Wt::WString::tr("Lms.PlayQueue.nb-tracks").arg(playlist->getCount()));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
PlayQueue::updateCurrentTrack(bool selected)
|
||||||
|
{
|
||||||
|
if (!_trackPos || *_trackPos >= static_cast<std::size_t>(_entriesContainer->count()))
|
||||||
|
return;
|
||||||
|
|
||||||
|
auto track = _entriesContainer->widget(*_trackPos);
|
||||||
|
if (track)
|
||||||
|
{
|
||||||
|
if (selected)
|
||||||
|
track->addStyleClass("Lms-playqueue-selected");
|
||||||
|
else
|
||||||
|
track->removeStyleClass("Lms-playqueue-selected");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
void
|
void
|
||||||
PlayQueue::addTracks(const std::vector<Database::Track::pointer>& tracks)
|
PlayQueue::addTracks(const std::vector<Database::Track::pointer>& tracks)
|
||||||
{
|
{
|
||||||
@@ -169,7 +244,12 @@ PlayQueue::addSome()
|
|||||||
|
|
||||||
auto playBtn = new Wt::WText(Wt::WString::tr("Lms.PlayQueue.play"), Wt::XHTMLText);
|
auto playBtn = new Wt::WText(Wt::WString::tr("Lms.PlayQueue.play"), Wt::XHTMLText);
|
||||||
entry->bindWidget("play-btn", playBtn);
|
entry->bindWidget("play-btn", playBtn);
|
||||||
// TODO
|
playBtn->clicked().connect(std::bind([=]
|
||||||
|
{
|
||||||
|
auto pos = _entriesContainer->indexOf(entry);
|
||||||
|
if (pos >= 0)
|
||||||
|
play(pos);
|
||||||
|
}));
|
||||||
|
|
||||||
auto delBtn = new Wt::WText(Wt::WString::tr("Lms.PlayQueue.delete"), Wt::XHTMLText);
|
auto delBtn = new Wt::WText(Wt::WString::tr("Lms.PlayQueue.delete"), Wt::XHTMLText);
|
||||||
entry->bindWidget("del-btn", delBtn);
|
entry->bindWidget("del-btn", delBtn);
|
||||||
@@ -183,6 +263,13 @@ PlayQueue::addSome()
|
|||||||
entryToRemove.remove();
|
entryToRemove.remove();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (_trackPos)
|
||||||
|
{
|
||||||
|
auto pos = _entriesContainer->indexOf(entry);
|
||||||
|
if (pos > 0 && *_trackPos >= static_cast<std::size_t>(pos))
|
||||||
|
(*_trackPos)--;
|
||||||
|
}
|
||||||
|
|
||||||
_entriesContainer->removeWidget(entry);
|
_entriesContainer->removeWidget(entry);
|
||||||
|
|
||||||
updateInfo();
|
updateInfo();
|
||||||
|
|||||||
@@ -20,9 +20,12 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include <Wt/WContainerWidget>
|
#include <Wt/WContainerWidget>
|
||||||
|
#include <Wt/WSignal>
|
||||||
#include <Wt/WTemplate>
|
#include <Wt/WTemplate>
|
||||||
#include <Wt/WText>
|
#include <Wt/WText>
|
||||||
|
|
||||||
|
#include <boost/optional.hpp>
|
||||||
|
|
||||||
#include "database/Types.hpp"
|
#include "database/Types.hpp"
|
||||||
|
|
||||||
namespace UserInterface {
|
namespace UserInterface {
|
||||||
@@ -35,13 +38,27 @@ class PlayQueue : public Wt::WContainerWidget
|
|||||||
void addTracks(const std::vector<Database::Track::pointer>& tracks);
|
void addTracks(const std::vector<Database::Track::pointer>& tracks);
|
||||||
void playTracks(const std::vector<Database::Track::pointer>& tracks);
|
void playTracks(const std::vector<Database::Track::pointer>& tracks);
|
||||||
|
|
||||||
|
// play the next track in the queue
|
||||||
|
void playNext();
|
||||||
|
|
||||||
|
// play the previous track in the queue
|
||||||
|
void playPrevious();
|
||||||
|
|
||||||
|
// Signal emitted when a track is to be played
|
||||||
|
Wt::Signal<Database::Track::id_type> playTrack;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
void addSome();
|
void addSome();
|
||||||
void updateInfo();
|
void updateInfo();
|
||||||
|
void updateCurrentTrack(bool selected);
|
||||||
|
|
||||||
|
void play(std::size_t pos);
|
||||||
|
void stop();
|
||||||
|
|
||||||
Wt::WContainerWidget* _entriesContainer;
|
Wt::WContainerWidget* _entriesContainer;
|
||||||
Wt::WTemplate* _showMore;
|
Wt::WTemplate* _showMore;
|
||||||
Wt::WText* _nbTracks;
|
Wt::WText* _nbTracks;
|
||||||
|
boost::optional<std::size_t> _trackPos; // current track position, if set
|
||||||
};
|
};
|
||||||
|
|
||||||
} // namespace UserInterface
|
} // namespace UserInterface
|
||||||
|
|||||||
Reference in New Issue
Block a user