[UI] Added a playqueue on mobile version, minor visual changes on desktop version
This commit is contained in:
@@ -35,6 +35,7 @@ lms_SOURCES = \
|
||||
$(srcdir)/ui/audio/mobile/ArtistSearchView.cpp \
|
||||
$(srcdir)/ui/audio/mobile/ArtistView.cpp \
|
||||
$(srcdir)/ui/audio/mobile/MobileAudio.cpp \
|
||||
$(srcdir)/ui/audio/mobile/MobilePlayQueue.cpp \
|
||||
$(srcdir)/ui/audio/mobile/PreviewSearchView.cpp \
|
||||
$(srcdir)/ui/audio/mobile/ReleaseSearch.cpp \
|
||||
$(srcdir)/ui/audio/mobile/ReleaseSearchView.cpp \
|
||||
|
||||
@@ -42,7 +42,6 @@ AudioPlayer::getBestEncoding() const
|
||||
return Av::Encoding::MP3;
|
||||
}
|
||||
|
||||
|
||||
bool
|
||||
AudioPlayer::loadTrack(Database::Track::id_type trackId)
|
||||
{
|
||||
@@ -121,7 +120,7 @@ AudioPlayer::loadTrack(Database::Track::id_type trackId)
|
||||
return true;
|
||||
}
|
||||
|
||||
AudioPlayer::AudioPlayer(Wt::WContainerWidget *parent)
|
||||
AudioPlayer::AudioPlayer(ControlFlags controls, Wt::WContainerWidget *parent)
|
||||
: Wt::WTemplate(parent)
|
||||
{
|
||||
setTemplateText(Wt::WString::tr("wa-audio-player"));
|
||||
@@ -154,42 +153,71 @@ AudioPlayer::AudioPlayer(Wt::WContainerWidget *parent)
|
||||
volumeSlider->setAttributeValue("orient", "vertical"); // firefox
|
||||
bindWidget("volume", volumeSlider);
|
||||
|
||||
Wt::WText *repeatBtn = new Wt::WText("<i class=\"fa fa-repeat fa-2x\"></i>", Wt::XHTMLText);
|
||||
repeatBtn->addStyleClass("mediaplayer-btn");
|
||||
bindWidget("repeat", repeatBtn);
|
||||
repeatBtn->clicked().connect(std::bind([=] ()
|
||||
if (controls & ControlRepeat)
|
||||
{
|
||||
if (repeatBtn->hasStyleClass("mediaplayer-btn-active"))
|
||||
setCondition("if-has-repeat", true);
|
||||
Wt::WText *repeatBtn = new Wt::WText("<i class=\"fa fa-repeat fa-2x\"></i>", Wt::XHTMLText);
|
||||
repeatBtn->addStyleClass("mediaplayer-btn hidden-xs");
|
||||
bindWidget("repeat", repeatBtn);
|
||||
repeatBtn->clicked().connect(std::bind([=] ()
|
||||
{
|
||||
repeatBtn->removeStyleClass("mediaplayer-btn-active");
|
||||
_loop.emit(false);
|
||||
}
|
||||
else
|
||||
{
|
||||
repeatBtn->addStyleClass("mediaplayer-btn-active");
|
||||
_loop.emit(true);
|
||||
}
|
||||
}));
|
||||
if (repeatBtn->hasStyleClass("mediaplayer-btn-active"))
|
||||
{
|
||||
repeatBtn->removeStyleClass("mediaplayer-btn-active");
|
||||
_loop.emit(false);
|
||||
}
|
||||
else
|
||||
{
|
||||
repeatBtn->addStyleClass("mediaplayer-btn-active");
|
||||
_loop.emit(true);
|
||||
}
|
||||
}));
|
||||
}
|
||||
|
||||
Wt::WText *shuffleBtn = new Wt::WText("<i class=\"fa fa-random fa-2x\"></i>", Wt::XHTMLText);
|
||||
shuffleBtn->addStyleClass("mediaplayer-btn");
|
||||
bindWidget("shuffle", shuffleBtn);
|
||||
shuffleBtn->clicked().connect(std::bind([=] ()
|
||||
if (controls & ControlPlayqueue)
|
||||
{
|
||||
if (shuffleBtn->hasStyleClass("mediaplayer-btn-active"))
|
||||
setCondition("if-has-playqueue", true);
|
||||
Wt::WText *playQueueBtn = new Wt::WText("<i class=\"fa fa-list-ul fa-2x\"></i>", Wt::XHTMLText);
|
||||
bindWidget("playqueue", playQueueBtn);
|
||||
playQueueBtn->addStyleClass("mediaplayer-btn");
|
||||
playQueueBtn->clicked().connect(std::bind([=] ()
|
||||
{
|
||||
shuffleBtn->removeStyleClass("mediaplayer-btn-active");
|
||||
_shuffle.emit(false);
|
||||
}
|
||||
else
|
||||
if (playQueueBtn->hasStyleClass("mediaplayer-btn-active"))
|
||||
{
|
||||
playQueueBtn->removeStyleClass("mediaplayer-btn-active");
|
||||
_playQueue.emit(false);
|
||||
}
|
||||
else
|
||||
{
|
||||
playQueueBtn->addStyleClass("mediaplayer-btn-active");
|
||||
_playQueue.emit(true);
|
||||
}
|
||||
}));
|
||||
}
|
||||
|
||||
if (controls & ControlShuffle)
|
||||
{
|
||||
setCondition("if-has-shuffle", true);
|
||||
Wt::WText *shuffleBtn = new Wt::WText("<i class=\"fa fa-random fa-2x\"></i>", Wt::XHTMLText);
|
||||
shuffleBtn->addStyleClass("mediaplayer-btn hidden-xs");
|
||||
bindWidget("shuffle", shuffleBtn);
|
||||
shuffleBtn->clicked().connect(std::bind([=] ()
|
||||
{
|
||||
shuffleBtn->addStyleClass("mediaplayer-btn-active");
|
||||
_shuffle.emit(true);
|
||||
}
|
||||
}));
|
||||
if (shuffleBtn->hasStyleClass("mediaplayer-btn-active"))
|
||||
{
|
||||
shuffleBtn->removeStyleClass("mediaplayer-btn-active");
|
||||
_shuffle.emit(false);
|
||||
}
|
||||
else
|
||||
{
|
||||
shuffleBtn->addStyleClass("mediaplayer-btn-active");
|
||||
_shuffle.emit(true);
|
||||
}
|
||||
}));
|
||||
}
|
||||
|
||||
Wt::WText *prevBtn = new Wt::WText("<i class=\"fa fa-step-backward fa-2x\"></i>", Wt::XHTMLText);
|
||||
prevBtn->addStyleClass("mediaplayer-btn hidden-xs");
|
||||
prevBtn->addStyleClass("mediaplayer-btn");
|
||||
bindWidget("prev", prevBtn);
|
||||
prevBtn->clicked().connect(std::bind([=] ()
|
||||
{
|
||||
|
||||
@@ -32,7 +32,15 @@ class AudioPlayer : public Wt::WTemplate
|
||||
{
|
||||
public:
|
||||
|
||||
AudioPlayer(Wt::WContainerWidget *parent = 0);
|
||||
enum ControlFlags
|
||||
{
|
||||
ControlNone = 0,
|
||||
ControlShuffle = 1 << 0,
|
||||
ControlRepeat = 1 << 1,
|
||||
ControlPlayqueue = 1 << 2,
|
||||
};
|
||||
|
||||
AudioPlayer(ControlFlags controls, Wt::WContainerWidget *parent = 0);
|
||||
|
||||
Av::Encoding getBestEncoding() const;
|
||||
bool loadTrack(Database::Track::id_type trackId);
|
||||
@@ -43,6 +51,7 @@ class AudioPlayer : public Wt::WTemplate
|
||||
Wt::Signal<void>& playPrevious() {return _playPrevious;}
|
||||
Wt::Signal<bool>& shuffle() {return _shuffle;}
|
||||
Wt::Signal<bool>& loop() {return _loop;}
|
||||
Wt::Signal<bool>& showPlayQueue() {return _playQueue;}
|
||||
|
||||
private:
|
||||
|
||||
@@ -52,6 +61,7 @@ class AudioPlayer : public Wt::WTemplate
|
||||
Wt::Signal<void> _playPrevious;
|
||||
Wt::Signal<bool> _shuffle;
|
||||
Wt::Signal<bool> _loop;
|
||||
Wt::Signal<bool> _playQueue;
|
||||
|
||||
Wt::WAudio* _audio;
|
||||
Wt::WText* _trackCurTime;
|
||||
@@ -60,4 +70,7 @@ class AudioPlayer : public Wt::WTemplate
|
||||
Wt::WImage* _cover;
|
||||
};
|
||||
|
||||
constexpr AudioPlayer::ControlFlags operator|(AudioPlayer::ControlFlags f1, AudioPlayer::ControlFlags f2) { return AudioPlayer::ControlFlags(int(f1)|int(f2)); }
|
||||
|
||||
|
||||
} // namespace UserInterface
|
||||
|
||||
@@ -149,16 +149,16 @@ _playQueue(nullptr)
|
||||
playlistBtn->setMenu(popupMain);
|
||||
}
|
||||
|
||||
Wt::WPushButton *upBtn = new Wt::WPushButton("UP");
|
||||
Wt::WPushButton *upBtn = new Wt::WPushButton("<i class =\"fa fa-arrow-up fa-lg\"></i>", Wt::XHTMLText);
|
||||
upBtn->setStyleClass("btn-sm");
|
||||
playlistControls->addWidget(upBtn);
|
||||
Wt::WPushButton *downBtn = new Wt::WPushButton("DO");
|
||||
Wt::WPushButton *downBtn = new Wt::WPushButton("<i class =\"fa fa-arrow-down fa-lg\"></i>", Wt::XHTMLText);
|
||||
downBtn->setStyleClass("btn-sm");
|
||||
playlistControls->addWidget(downBtn);
|
||||
Wt::WPushButton *delBtn = new Wt::WPushButton("DEL");
|
||||
Wt::WPushButton *delBtn = new Wt::WPushButton("<i class =\"fa fa-remove fa-lg\"></i>", Wt::XHTMLText);
|
||||
delBtn->setStyleClass("btn-sm btn-warning");
|
||||
playlistControls->addWidget(delBtn);
|
||||
Wt::WPushButton *clearBtn = new Wt::WPushButton("CLR");
|
||||
Wt::WPushButton *clearBtn = new Wt::WPushButton("<i class =\"fa fa-trash fa-lg\"></i>", Wt::XHTMLText);
|
||||
clearBtn->setStyleClass("btn-sm btn-danger");
|
||||
playlistControls->addWidget(clearBtn);
|
||||
|
||||
@@ -180,7 +180,7 @@ _playQueue(nullptr)
|
||||
_playQueue->select(CurrentUser()->getCurPlayingTrackPos());
|
||||
}
|
||||
|
||||
_mediaPlayer = new AudioPlayer();
|
||||
_mediaPlayer = new AudioPlayer(AudioPlayer::ControlShuffle | AudioPlayer::ControlRepeat);
|
||||
mainLayout->addWidget(_mediaPlayer, 2, 0, 1, 4);
|
||||
|
||||
mainLayout->setRowStretch(1, 1);
|
||||
|
||||
@@ -55,7 +55,7 @@ TableFilterGenre::TableFilterGenre(Wt::WContainerWidget* parent)
|
||||
|
||||
this->setLayoutSizeAware(true);
|
||||
|
||||
_queryModel.setBatchSize(100);
|
||||
_queryModel.setBatchSize(150);
|
||||
|
||||
// If an item is double clicked, select and emit signal
|
||||
this->doubleClicked().connect( std::bind([=] (Wt::WModelIndex idx, Wt::WMouseEvent evt)
|
||||
@@ -134,7 +134,7 @@ TableFilterArtist::TableFilterArtist(Wt::WContainerWidget* parent)
|
||||
|
||||
this->setLayoutSizeAware(true);
|
||||
|
||||
_queryModel.setBatchSize(100);
|
||||
_queryModel.setBatchSize(150);
|
||||
|
||||
// If an item is double clicked, select and emit signal
|
||||
this->doubleClicked().connect( std::bind([=] (Wt::WModelIndex idx, Wt::WMouseEvent evt)
|
||||
@@ -218,7 +218,7 @@ TableFilterRelease::TableFilterRelease(Wt::WContainerWidget* parent)
|
||||
|
||||
this->setLayoutSizeAware(true);
|
||||
|
||||
_queryModel.setBatchSize(100);
|
||||
_queryModel.setBatchSize(150);
|
||||
|
||||
// If an item is double clicked, select and emit signal
|
||||
this->doubleClicked().connect( std::bind([=] (Wt::WModelIndex idx, Wt::WMouseEvent evt)
|
||||
|
||||
@@ -70,7 +70,7 @@ TrackView::TrackView(Wt::WContainerWidget* parent)
|
||||
|
||||
Track::updateUIQueryModel(DboSession(), _queryModel, filter, columnNames);
|
||||
|
||||
_queryModel.setBatchSize(300);
|
||||
_queryModel.setBatchSize(500);
|
||||
|
||||
this->setSortingEnabled(true);
|
||||
this->setSelectionMode(Wt::ExtendedSelection);
|
||||
|
||||
@@ -37,6 +37,8 @@
|
||||
#include "ArtistView.hpp"
|
||||
#include "ReleaseView.hpp"
|
||||
|
||||
#include "MobilePlayQueue.hpp"
|
||||
|
||||
#include "MobileAudio.hpp"
|
||||
|
||||
|
||||
@@ -51,6 +53,7 @@ enum WidgetIdx
|
||||
WidgetIdxSearchTrack = 3,
|
||||
WidgetIdxArtist = 4,
|
||||
WidgetIdxRelease = 5,
|
||||
WidgetIdxPlayQueue = 6,
|
||||
};
|
||||
|
||||
using namespace Database;
|
||||
@@ -70,6 +73,8 @@ Audio::Audio(Wt::WContainerWidget *parent)
|
||||
|
||||
Wt::WStackedWidget *stack = new Wt::WStackedWidget(this);
|
||||
|
||||
PlayQueue *playQueue = new PlayQueue();
|
||||
|
||||
// Same order as WidgetIdxXXX
|
||||
stack->addWidget(new PreviewSearchView(_playQueueEvents));
|
||||
stack->addWidget(new ArtistSearchView());
|
||||
@@ -77,6 +82,7 @@ Audio::Audio(Wt::WContainerWidget *parent)
|
||||
stack->addWidget(new TrackSearchView(_playQueueEvents));
|
||||
stack->addWidget(new ArtistView());
|
||||
stack->addWidget(new ReleaseView(_playQueueEvents));
|
||||
stack->addWidget(playQueue);
|
||||
|
||||
wApp->internalPathChanged().connect(std::bind([=] (std::string path)
|
||||
{
|
||||
@@ -89,6 +95,7 @@ Audio::Audio(Wt::WContainerWidget *parent)
|
||||
{ "/audio/search/track", WidgetIdxSearchTrack},
|
||||
{ "/audio/artist", WidgetIdxArtist},
|
||||
{ "/audio/release", WidgetIdxRelease},
|
||||
{ "/audio/playqueue", WidgetIdxPlayQueue},
|
||||
};
|
||||
|
||||
for (auto index : indexes)
|
||||
@@ -102,14 +109,48 @@ Audio::Audio(Wt::WContainerWidget *parent)
|
||||
Wt::WTemplate* footer = new Wt::WTemplate(this);
|
||||
footer->setTemplateText(Wt::WString::tr("mobile-audio-footer"));
|
||||
|
||||
AudioPlayer* audioPlayer = new AudioPlayer();
|
||||
AudioPlayer* audioPlayer = new AudioPlayer(AudioPlayer::ControlPlayqueue);
|
||||
footer->bindWidget("player", audioPlayer);
|
||||
|
||||
audioPlayer->showPlayQueue().connect(std::bind([=] (bool state)
|
||||
{
|
||||
if (state)
|
||||
wApp->setInternalPath("/audio/playqueue", true);
|
||||
}, std::placeholders::_1));
|
||||
|
||||
// Connect the audio player events to the playqueue
|
||||
playQueue->playTrack().connect(std::bind([=] (Database::Track::id_type id)
|
||||
{
|
||||
audioPlayer->loadTrack(id);
|
||||
}, std::placeholders::_1));
|
||||
audioPlayer->playNext().connect(std::bind([=]
|
||||
{
|
||||
playQueue->playNext();
|
||||
}));
|
||||
audioPlayer->playPrevious().connect(std::bind([=]
|
||||
{
|
||||
playQueue->playPrevious();
|
||||
}));
|
||||
audioPlayer->playbackEnded().connect(std::bind([=]
|
||||
{
|
||||
playQueue->playNext();
|
||||
}));
|
||||
audioPlayer->shuffle().connect(std::bind(&PlayQueue::setShuffle, playQueue, std::placeholders::_1));
|
||||
audioPlayer->loop().connect(std::bind(&PlayQueue::setLoop, playQueue, std::placeholders::_1));
|
||||
|
||||
// Connect the events to the player
|
||||
_playQueueEvents.trackPlay.connect(std::bind([=] (Database::Track::id_type id)
|
||||
{
|
||||
audioPlayer->loadTrack(id);
|
||||
std::size_t pos = playQueue->addTrack(id);
|
||||
playQueue->play(pos);
|
||||
}, std::placeholders::_1));
|
||||
|
||||
_playQueueEvents.trackAdd.connect(std::bind([=] (Database::Track::id_type id)
|
||||
{
|
||||
playQueue->addTrack(id);
|
||||
}, std::placeholders::_1));
|
||||
|
||||
|
||||
}
|
||||
|
||||
} // namespace Mobile
|
||||
|
||||
@@ -0,0 +1,206 @@
|
||||
/*
|
||||
* Copyright (C) 2016 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 <Wt/WTemplate>
|
||||
#include <Wt/WImage>
|
||||
#include <Wt/WText>
|
||||
#include <Wt/WPushButton>
|
||||
|
||||
#include "logger/Logger.hpp"
|
||||
|
||||
#include "LmsApplication.hpp"
|
||||
|
||||
#include "MobilePlayQueue.hpp"
|
||||
|
||||
|
||||
namespace UserInterface {
|
||||
namespace Mobile {
|
||||
|
||||
|
||||
PlayQueue::PlayQueue(Wt::WContainerWidget* parent)
|
||||
: Wt::WContainerWidget(parent)
|
||||
{
|
||||
Wt::WTemplate* t = new Wt::WTemplate(this);
|
||||
t->setTemplateText(Wt::WString::tr("wa-playqueue-view"));
|
||||
|
||||
_trackContainer = new Wt::WContainerWidget();
|
||||
t->bindWidget("contents", _trackContainer);
|
||||
|
||||
Wt::WTemplate* controls = new Wt::WTemplate();
|
||||
t->bindWidget("controls", controls);
|
||||
controls->setTemplateText(Wt::WString::tr("wa-playqueue-controls"));
|
||||
|
||||
Wt::WText *loadBtn = new Wt::WText("<i class=\"fa fa-upload fa-2x\"></i>", Wt::XHTMLText);
|
||||
controls->bindWidget("load", loadBtn);
|
||||
loadBtn->setStyleClass("mobile-btn");
|
||||
|
||||
Wt::WText *saveBtn = new Wt::WText("<i class=\"fa fa-download fa-2x\"></i>", Wt::XHTMLText);
|
||||
controls->bindWidget("save", saveBtn);
|
||||
saveBtn->setStyleClass("mobile-btn");
|
||||
|
||||
Wt::WText *clearBtn = new Wt::WText("<i class=\"fa fa-remove fa-2x\"></i>", Wt::XHTMLText);
|
||||
controls->bindWidget("clear", clearBtn);
|
||||
clearBtn->setStyleClass("mobile-btn");
|
||||
clearBtn->clicked().connect(std::bind([=]
|
||||
{
|
||||
clear();
|
||||
}));
|
||||
|
||||
_shuffleBtn = new Wt::WText("<i class=\"fa fa-random fa-2x\"></i>", Wt::XHTMLText);
|
||||
controls->bindWidget("shuffle", _shuffleBtn);
|
||||
_shuffleBtn->setStyleClass("mobile-btn");
|
||||
_shuffleBtn->clicked().connect(std::bind([=] ()
|
||||
{
|
||||
setShuffle(!_shuffle);
|
||||
}));
|
||||
|
||||
_loopBtn = new Wt::WText("<i class=\"fa fa-repeat fa-2x\"></i>", Wt::XHTMLText);
|
||||
controls->bindWidget("repeat", _loopBtn);
|
||||
_loopBtn->setStyleClass("mobile-btn");
|
||||
_loopBtn->clicked().connect(std::bind([=] ()
|
||||
{
|
||||
setLoop(!_loop);
|
||||
}));
|
||||
|
||||
}
|
||||
|
||||
std::size_t
|
||||
PlayQueue::addTrack(Database::Track::id_type id)
|
||||
{
|
||||
Wt::Dbo::Transaction transaction(DboSession());
|
||||
|
||||
Database::Track::pointer track = Database::Track::getById(DboSession(), id);
|
||||
if (!track)
|
||||
{
|
||||
LMS_LOG(UI, INFO) << "No track found for id " << id;
|
||||
return 0;
|
||||
}
|
||||
|
||||
std::size_t trackPos = _trackIds.size();
|
||||
_trackIds.push_back(id);
|
||||
|
||||
Wt::WTemplate* t = new Wt::WTemplate(_trackContainer);
|
||||
t->setTemplateText(Wt::WString::tr("wa-playqueue-track"));
|
||||
|
||||
Wt::WImage *cover = new Wt::WImage();
|
||||
t->bindWidget("cover", cover);
|
||||
cover->setStyleClass ("center-block img-responsive");
|
||||
cover->setImageLink(SessionImageResource()->getTrackUrl(track.id(), 64));
|
||||
t->bindString("track-name", Wt::WString::fromUTF8(track->getName()), Wt::PlainText);
|
||||
t->bindString("artist-name", Wt::WString::fromUTF8(track->getArtist()->getName()), Wt::PlainText);
|
||||
|
||||
Wt::WText *playBtn = new Wt::WText("<i class=\"fa fa-play fa-lg\"></i>", Wt::XHTMLText);
|
||||
t->bindWidget("play-btn", playBtn);
|
||||
playBtn->addStyleClass("mobile-btn");
|
||||
playBtn->clicked().connect(std::bind([=]
|
||||
{
|
||||
int pos = _trackContainer->indexOf(t);
|
||||
play(pos);
|
||||
}));
|
||||
|
||||
Wt::WText *delBtn = new Wt::WText("<i class=\"fa fa-remove fa-lg\"></i>", Wt::XHTMLText);
|
||||
t->bindWidget("del-btn", delBtn);
|
||||
delBtn->addStyleClass("mobile-btn");
|
||||
delBtn->clicked().connect(std::bind([=]
|
||||
{
|
||||
int pos = _trackContainer->indexOf(t);
|
||||
_trackContainer->removeWidget(t);
|
||||
_trackIds.erase(_trackIds.begin() + pos);
|
||||
}));
|
||||
|
||||
return trackPos;
|
||||
}
|
||||
|
||||
void
|
||||
PlayQueue::clear(void)
|
||||
{
|
||||
_trackIds.clear();
|
||||
_trackContainer->clear();
|
||||
_currentPos = 0;
|
||||
}
|
||||
|
||||
void
|
||||
PlayQueue::play(size_t pos)
|
||||
{
|
||||
if (_trackIds.empty() || pos >= _trackIds.size())
|
||||
return;
|
||||
|
||||
_trackContainer->widget(_currentPos)->removeStyleClass("playqueue-playing");
|
||||
|
||||
_currentPos = pos;
|
||||
_trackContainer->widget(_currentPos)->addStyleClass("playqueue-playing");
|
||||
|
||||
_sigTrackPlay.emit(_trackIds[pos]);
|
||||
}
|
||||
|
||||
void
|
||||
PlayQueue::playNext(void)
|
||||
{
|
||||
if (_currentPos == (_trackIds.size() - 1) && _loop)
|
||||
play(0);
|
||||
else
|
||||
play(_currentPos + 1);
|
||||
}
|
||||
|
||||
void
|
||||
PlayQueue::playPrevious(void)
|
||||
{
|
||||
if (_currentPos == 0)
|
||||
{
|
||||
if (_loop)
|
||||
{
|
||||
play(_trackIds.size() - 1);
|
||||
}
|
||||
else
|
||||
return;
|
||||
}
|
||||
else
|
||||
play(_currentPos - 1);
|
||||
}
|
||||
|
||||
void
|
||||
PlayQueue::handlePlaybackComplete(void)
|
||||
{
|
||||
playNext();
|
||||
}
|
||||
|
||||
void
|
||||
PlayQueue::setShuffle(bool value)
|
||||
{
|
||||
_shuffle = value;
|
||||
|
||||
if (value)
|
||||
_shuffleBtn->addStyleClass("mobile-btn-active");
|
||||
else
|
||||
_shuffleBtn->removeStyleClass("mobile-btn-active");
|
||||
}
|
||||
|
||||
void
|
||||
PlayQueue::setLoop(bool value)
|
||||
{
|
||||
_loop = value;
|
||||
|
||||
if (value)
|
||||
_loopBtn->addStyleClass("mobile-btn-active");
|
||||
else
|
||||
_loopBtn->removeStyleClass("mobile-btn-active");
|
||||
}
|
||||
|
||||
} // namespace Mobile
|
||||
} // namespace UserInterface
|
||||
@@ -0,0 +1,75 @@
|
||||
/*
|
||||
* Copyright (C) 2016 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/>.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <vector>
|
||||
|
||||
#include <Wt/WContainerWidget>
|
||||
#include <Wt/WSignal>
|
||||
|
||||
#include "database/Types.hpp"
|
||||
|
||||
namespace UserInterface {
|
||||
namespace Mobile {
|
||||
|
||||
class PlayQueue : public Wt::WContainerWidget
|
||||
{
|
||||
public:
|
||||
PlayQueue(Wt::WContainerWidget* parent = 0);
|
||||
|
||||
// Contents
|
||||
std::size_t addArtist(Database::Artist::id_type id);
|
||||
std::size_t addRelease(Database::Release::id_type id);
|
||||
std::size_t addTrack(Database::Track::id_type id);
|
||||
void clear(void);
|
||||
|
||||
// Controls
|
||||
void play(size_t pos); // Play the track at a given position
|
||||
void playNext(void);
|
||||
void playPrevious(void);
|
||||
|
||||
void setShuffle(bool enable); // true to enable shuffle
|
||||
void setLoop(bool enable); // true to enable loop
|
||||
|
||||
// Signals
|
||||
// Emitted when a song has to be played
|
||||
Wt::Signal<Database::Track::id_type>& playTrack() { return _sigTrackPlay; }
|
||||
|
||||
// Slots
|
||||
// Song has finished
|
||||
void handlePlaybackComplete(void);
|
||||
|
||||
private:
|
||||
|
||||
bool _loop = false;
|
||||
bool _shuffle = false;
|
||||
std::size_t _currentPos = 0;
|
||||
|
||||
Wt::WText* _shuffleBtn = nullptr;
|
||||
Wt::WText* _loopBtn = nullptr;
|
||||
|
||||
Wt::Signal<Database::Track::id_type> _sigTrackPlay;
|
||||
|
||||
std::vector<Database::Track::id_type> _trackIds;
|
||||
Wt::WContainerWidget* _trackContainer;
|
||||
};
|
||||
|
||||
} // namespace Mobile
|
||||
} // namespace UserInterface
|
||||
@@ -28,6 +28,7 @@ namespace Mobile {
|
||||
struct PlayQueueEvents
|
||||
{
|
||||
Wt::Signal<Database::Track::id_type> trackPlay;
|
||||
Wt::Signal<Database::Track::id_type> trackAdd;
|
||||
};
|
||||
|
||||
|
||||
|
||||
@@ -177,13 +177,23 @@ ReleaseView::addResults(size_t nb)
|
||||
trackRes->bindString("track-name", Wt::WString::fromUTF8(track->getName()), Wt::PlainText);
|
||||
// TODO, display artist name for compilation releases?
|
||||
|
||||
Wt::WText *playBtn = new Wt::WText("Play", Wt::PlainText);
|
||||
playBtn->setStyleClass("center-block"); // TODO move to CSS?
|
||||
Wt::WText *playBtn = new Wt::WText("<i class=\"fa fa-play fa-lg\"></i>", Wt::XHTMLText);
|
||||
trackRes->bindWidget("play-btn", playBtn);
|
||||
playBtn->setStyleClass("mobile-btn");
|
||||
playBtn->clicked().connect(std::bind([=] {
|
||||
_events.trackPlay.emit(track.id());
|
||||
}));
|
||||
|
||||
trackRes->bindWidget("play-btn", playBtn);
|
||||
Wt::WText *addBtn = new Wt::WText("<i class=\"fa fa-plus fa-lg\"></i>", Wt::XHTMLText);
|
||||
trackRes->bindWidget("add-btn", addBtn);
|
||||
addBtn->setStyleClass("mobile-btn");
|
||||
addBtn->clicked().connect(std::bind([=] {
|
||||
_events.trackAdd.emit(track.id());
|
||||
}));
|
||||
|
||||
|
||||
|
||||
|
||||
_nbTracks++;
|
||||
}
|
||||
|
||||
|
||||
@@ -89,19 +89,27 @@ TrackSearch::addResults(size_t nb)
|
||||
res->setTemplateText(Wt::WString::tr("wa-track-search-res"));
|
||||
|
||||
Wt::WImage *cover = new Wt::WImage();
|
||||
res->bindWidget("cover", cover);
|
||||
cover->setStyleClass ("center-block img-responsive");
|
||||
cover->setImageLink(SessionImageResource()->getTrackUrl(track.id(), 64));
|
||||
res->bindWidget("cover", cover);
|
||||
res->bindString("track-name", Wt::WString::fromUTF8(track->getName()), Wt::PlainText);
|
||||
res->bindString("artist-name", Wt::WString::fromUTF8(track->getArtist()->getName()), Wt::PlainText);
|
||||
|
||||
Wt::WText *playBtn = new Wt::WText("Play", Wt::PlainText);
|
||||
playBtn->setStyleClass("center-block"); // TODO move to CSS?
|
||||
Wt::WText *playBtn = new Wt::WText("<i class=\"fa fa-play fa-lg\"></i>", Wt::XHTMLText);
|
||||
res->bindWidget("play-btn", playBtn);
|
||||
playBtn->setStyleClass("mobile-btn");
|
||||
playBtn->clicked().connect(std::bind([=] {
|
||||
_events.trackPlay.emit(track.id());
|
||||
}));
|
||||
|
||||
res->bindWidget("btn", playBtn);
|
||||
Wt::WText *addBtn = new Wt::WText("<i class=\"fa fa-plus fa-lg\"></i>", Wt::XHTMLText);
|
||||
res->bindWidget("add-btn", addBtn);
|
||||
addBtn->setStyleClass("mobile-btn");
|
||||
addBtn->clicked().connect(std::bind([=] {
|
||||
_events.trackAdd.emit(track.id());
|
||||
}));
|
||||
|
||||
|
||||
}
|
||||
|
||||
if (moreResults)
|
||||
|
||||
Reference in New Issue
Block a user