From 5006a82ce646f5bf6ec8cf2b945e147009fb453f Mon Sep 17 00:00:00 2001 From: emeric Date: Mon, 19 Mar 2018 13:45:09 +0100 Subject: [PATCH] Added some logic in the playqueue (play next / play previous handling) --- docroot/css/lms.css | 188 +------------------------------------- src/database/Playlist.cpp | 13 +++ src/database/Playlist.hpp | 1 + src/ui/LmsApplication.cpp | 17 ++++ src/ui/MediaPlayer.cpp | 15 +++ src/ui/MediaPlayer.hpp | 11 +++ src/ui/PlayQueueView.cpp | 89 +++++++++++++++++- src/ui/PlayQueueView.hpp | 17 ++++ 8 files changed, 163 insertions(+), 188 deletions(-) diff --git a/docroot/css/lms.css b/docroot/css/lms.css index 9c47a5dc..47303070 100644 --- a/docroot/css/lms.css +++ b/docroot/css/lms.css @@ -1,191 +1,5 @@ -.main-nav { - margin-bottom: 0px; -} -.playqueue { - background-color: #EEE; - border-radius: 10px; - box-shadow: 0px 2px 3px rgba(0, 0, 0, 0.4); -} - -.playqueue-playing { +.Lms-playqueue-selected { 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) -} - diff --git a/src/database/Playlist.cpp b/src/database/Playlist.cpp index d22e3ff2..f29e4927 100644 --- a/src/database/Playlist.cpp +++ b/src/database/Playlist.cpp @@ -109,6 +109,19 @@ Playlist::getEntries(int offset, int size, bool& moreResults) const return res; } +Wt::Dbo::ptr +Playlist::getEntry(std::size_t pos) const +{ + Wt::Dbo::ptr res; + + bool moreResults; + auto entries = getEntries(pos, 1, moreResults); + if (!entries.empty()) + res = entries.front(); + + return res; +} + std::size_t Playlist::getCount() const { diff --git a/src/database/Playlist.hpp b/src/database/Playlist.hpp index 1c43cde8..4e2b1f04 100644 --- a/src/database/Playlist.hpp +++ b/src/database/Playlist.hpp @@ -54,6 +54,7 @@ class Playlist : public Wt::Dbo::Dbo // Get tracks, ordered by position std::size_t getCount() const; + Wt::Dbo::ptr getEntry(std::size_t pos) const; std::vector> getEntries(int offset, int size, bool& moreResults) const; template diff --git a/src/ui/LmsApplication.cpp b/src/ui/LmsApplication.cpp index 4c9032e8..1c95261c 100644 --- a/src/ui/LmsApplication.cpp +++ b/src/ui/LmsApplication.cpp @@ -340,6 +340,23 @@ LmsApplication::handleAuthEvent(void) auto player = new MediaPlayer(); 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 if (CurrentUser()->isAdmin()) { diff --git a/src/ui/MediaPlayer.cpp b/src/ui/MediaPlayer.cpp index 23d603c6..f891e04b 100644 --- a/src/ui/MediaPlayer.cpp +++ b/src/ui/MediaPlayer.cpp @@ -21,6 +21,8 @@ #include #include +#include "utils/Logger.hpp" + #include "MediaPlayer.hpp" namespace UserInterface { @@ -35,9 +37,17 @@ MediaPlayer::MediaPlayer(Wt::WContainerWidget* parent) auto prevBtn = new Wt::WText(Wt::WString::tr("btn-mediaplayer-prev"), Wt::XHTMLText); player->bindWidget("previous", prevBtn); + prevBtn->clicked().connect(std::bind([=] + { + playPrevious.emit(); + })); auto nextBtn = new Wt::WText(Wt::WString::tr("btn-mediaplayer-next"), Wt::XHTMLText); player->bindWidget("next", nextBtn); + nextBtn->clicked().connect(std::bind([=] + { + playNext.emit(); + })); auto shuffleBtn = new Wt::WText(Wt::WString::tr("btn-mediaplayer-shuffle"), Wt::XHTMLText); player->bindWidget("shuffle", shuffleBtn); @@ -53,7 +63,12 @@ MediaPlayer::MediaPlayer(Wt::WContainerWidget* parent) auto trackName = new Wt::WText("---"); player->bindWidget("track-name", trackName); +} +void +MediaPlayer::playTrack(Database::Track::id_type id) +{ + LMS_LOG(UI, DEBUG) << "Playing track ID = " << id; } } // namespace UserInterface diff --git a/src/ui/MediaPlayer.hpp b/src/ui/MediaPlayer.hpp index 69615b68..8cb013d6 100644 --- a/src/ui/MediaPlayer.hpp +++ b/src/ui/MediaPlayer.hpp @@ -19,14 +19,25 @@ #pragma once +#include #include +#include "database/Types.hpp" + namespace UserInterface { class MediaPlayer : public Wt::WContainerWidget { public: MediaPlayer(Wt::WContainerWidget* parent = 0); + + void playTrack(Database::Track::id_type); + + // Signals + Wt::Signal playbackEnded; + Wt::Signal playPrevious; + Wt::Signal playNext; + }; } // namespace UserInterface diff --git a/src/ui/PlayQueueView.cpp b/src/ui/PlayQueueView.cpp index 27926ec0..857ba4f8 100644 --- a/src/ui/PlayQueueView.cpp +++ b/src/ui/PlayQueueView.cpp @@ -75,6 +75,8 @@ PlayQueue::PlayQueue(Wt::WContainerWidget* parent) auto playlist = Database::Playlist::get(DboSession(), currentPlayQueueName, CurrentUser()); if (!playlist) playlist = Database::Playlist::create(DboSession(), currentPlayQueueName, false, CurrentUser()); + + _trackPos = CurrentUser()->getCurPlayingTrackPos(); } _showMore->clicked().connect(std::bind([=] @@ -86,6 +88,63 @@ PlayQueue::PlayQueue(Wt::WContainerWidget* parent) 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 PlayQueue::updateInfo() { @@ -95,6 +154,22 @@ PlayQueue::updateInfo() _nbTracks->setText(Wt::WString::tr("Lms.PlayQueue.nb-tracks").arg(playlist->getCount())); } +void +PlayQueue::updateCurrentTrack(bool selected) +{ + if (!_trackPos || *_trackPos >= static_cast(_entriesContainer->count())) + return; + + auto track = _entriesContainer->widget(*_trackPos); + if (track) + { + if (selected) + track->addStyleClass("Lms-playqueue-selected"); + else + track->removeStyleClass("Lms-playqueue-selected"); + } +} + void PlayQueue::addTracks(const std::vector& tracks) { @@ -169,7 +244,12 @@ PlayQueue::addSome() auto playBtn = new Wt::WText(Wt::WString::tr("Lms.PlayQueue.play"), Wt::XHTMLText); 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); entry->bindWidget("del-btn", delBtn); @@ -183,6 +263,13 @@ PlayQueue::addSome() entryToRemove.remove(); } + if (_trackPos) + { + auto pos = _entriesContainer->indexOf(entry); + if (pos > 0 && *_trackPos >= static_cast(pos)) + (*_trackPos)--; + } + _entriesContainer->removeWidget(entry); updateInfo(); diff --git a/src/ui/PlayQueueView.hpp b/src/ui/PlayQueueView.hpp index 6975a0df..13628cd9 100644 --- a/src/ui/PlayQueueView.hpp +++ b/src/ui/PlayQueueView.hpp @@ -20,9 +20,12 @@ #pragma once #include +#include #include #include +#include + #include "database/Types.hpp" namespace UserInterface { @@ -35,13 +38,27 @@ class PlayQueue : public Wt::WContainerWidget void addTracks(const std::vector& tracks); void playTracks(const std::vector& 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 playTrack; + private: void addSome(); void updateInfo(); + void updateCurrentTrack(bool selected); + + void play(std::size_t pos); + void stop(); Wt::WContainerWidget* _entriesContainer; Wt::WTemplate* _showMore; Wt::WText* _nbTracks; + boost::optional _trackPos; // current track position, if set }; } // namespace UserInterface