@@ -426,7 +417,7 @@
+
+ ${controls}
+
+
+
+
+ ${load}${save}${clear}${shuffle}${repeat}
+
+
+
+
+
+
${cover}
+
${artist-name}
${track-name}
+
${play-btn}${del-btn}
+
+
${audio}
${prev}${play-pause}${next}
-
- ${shuffle}${repeat}
+ ${}${shuffle}${}${}${repeat}${}${}${playqueue}${}
diff --git a/docroot/css/lms.css b/docroot/css/lms.css
index 6c0bf27d..814622af 100644
--- a/docroot/css/lms.css
+++ b/docroot/css/lms.css
@@ -129,6 +129,19 @@
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;
@@ -198,3 +211,13 @@
max-width: 100%;
height: auto;
}
+
+.release-search-cover {
+ width: 512px;
+ min-width: 512px;
+ height: 512px;
+ min-height: 512px;
+ display: flex;
+ justify-content: space-between;
+ align-items: center;
+}
diff --git a/src/Makefile.am b/src/Makefile.am
index b5c5e7d6..5b9e6480 100644
--- a/src/Makefile.am
+++ b/src/Makefile.am
@@ -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 \
diff --git a/src/ui/audio/AudioPlayer.cpp b/src/ui/audio/AudioPlayer.cpp
index 1df7115e..1cad4b2c 100644
--- a/src/ui/audio/AudioPlayer.cpp
+++ b/src/ui/audio/AudioPlayer.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("
", 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("
", 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("
", 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("
", 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("
", 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("
", Wt::XHTMLText);
- prevBtn->addStyleClass("mediaplayer-btn hidden-xs");
+ prevBtn->addStyleClass("mediaplayer-btn");
bindWidget("prev", prevBtn);
prevBtn->clicked().connect(std::bind([=] ()
{
diff --git a/src/ui/audio/AudioPlayer.hpp b/src/ui/audio/AudioPlayer.hpp
index 371fe2b3..867824a9 100644
--- a/src/ui/audio/AudioPlayer.hpp
+++ b/src/ui/audio/AudioPlayer.hpp
@@ -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
& playPrevious() {return _playPrevious;}
Wt::Signal& shuffle() {return _shuffle;}
Wt::Signal& loop() {return _loop;}
+ Wt::Signal& showPlayQueue() {return _playQueue;}
private:
@@ -52,6 +61,7 @@ class AudioPlayer : public Wt::WTemplate
Wt::Signal _playPrevious;
Wt::Signal _shuffle;
Wt::Signal _loop;
+ Wt::Signal _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
diff --git a/src/ui/audio/desktop/DesktopAudio.cpp b/src/ui/audio/desktop/DesktopAudio.cpp
index efcd13c3..3432957e 100644
--- a/src/ui/audio/desktop/DesktopAudio.cpp
+++ b/src/ui/audio/desktop/DesktopAudio.cpp
@@ -149,16 +149,16 @@ _playQueue(nullptr)
playlistBtn->setMenu(popupMain);
}
- Wt::WPushButton *upBtn = new Wt::WPushButton("UP");
+ Wt::WPushButton *upBtn = new Wt::WPushButton("", Wt::XHTMLText);
upBtn->setStyleClass("btn-sm");
playlistControls->addWidget(upBtn);
- Wt::WPushButton *downBtn = new Wt::WPushButton("DO");
+ Wt::WPushButton *downBtn = new Wt::WPushButton("", Wt::XHTMLText);
downBtn->setStyleClass("btn-sm");
playlistControls->addWidget(downBtn);
- Wt::WPushButton *delBtn = new Wt::WPushButton("DEL");
+ Wt::WPushButton *delBtn = new Wt::WPushButton("", Wt::XHTMLText);
delBtn->setStyleClass("btn-sm btn-warning");
playlistControls->addWidget(delBtn);
- Wt::WPushButton *clearBtn = new Wt::WPushButton("CLR");
+ Wt::WPushButton *clearBtn = new Wt::WPushButton("", 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);
diff --git a/src/ui/audio/desktop/TableFilter.cpp b/src/ui/audio/desktop/TableFilter.cpp
index f7197779..6438f713 100644
--- a/src/ui/audio/desktop/TableFilter.cpp
+++ b/src/ui/audio/desktop/TableFilter.cpp
@@ -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)
diff --git a/src/ui/audio/desktop/TrackView.cpp b/src/ui/audio/desktop/TrackView.cpp
index d01a48ef..3c3e9cc6 100644
--- a/src/ui/audio/desktop/TrackView.cpp
+++ b/src/ui/audio/desktop/TrackView.cpp
@@ -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);
diff --git a/src/ui/audio/mobile/MobileAudio.cpp b/src/ui/audio/mobile/MobileAudio.cpp
index 9399264f..0f8b036a 100644
--- a/src/ui/audio/mobile/MobileAudio.cpp
+++ b/src/ui/audio/mobile/MobileAudio.cpp
@@ -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
diff --git a/src/ui/audio/mobile/MobilePlayQueue.cpp b/src/ui/audio/mobile/MobilePlayQueue.cpp
new file mode 100644
index 00000000..e74ddb9a
--- /dev/null
+++ b/src/ui/audio/mobile/MobilePlayQueue.cpp
@@ -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 .
+ */
+
+#include
+#include
+#include
+#include
+
+#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("", Wt::XHTMLText);
+ controls->bindWidget("load", loadBtn);
+ loadBtn->setStyleClass("mobile-btn");
+
+ Wt::WText *saveBtn = new Wt::WText("", Wt::XHTMLText);
+ controls->bindWidget("save", saveBtn);
+ saveBtn->setStyleClass("mobile-btn");
+
+ Wt::WText *clearBtn = new Wt::WText("", Wt::XHTMLText);
+ controls->bindWidget("clear", clearBtn);
+ clearBtn->setStyleClass("mobile-btn");
+ clearBtn->clicked().connect(std::bind([=]
+ {
+ clear();
+ }));
+
+ _shuffleBtn = new Wt::WText("", Wt::XHTMLText);
+ controls->bindWidget("shuffle", _shuffleBtn);
+ _shuffleBtn->setStyleClass("mobile-btn");
+ _shuffleBtn->clicked().connect(std::bind([=] ()
+ {
+ setShuffle(!_shuffle);
+ }));
+
+ _loopBtn = new Wt::WText("", 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("", 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("", 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
diff --git a/src/ui/audio/mobile/MobilePlayQueue.hpp b/src/ui/audio/mobile/MobilePlayQueue.hpp
new file mode 100644
index 00000000..ab8a1dfc
--- /dev/null
+++ b/src/ui/audio/mobile/MobilePlayQueue.hpp
@@ -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 .
+ */
+
+#pragma once
+
+#include
+
+#include
+#include
+
+#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& 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 _sigTrackPlay;
+
+ std::vector _trackIds;
+ Wt::WContainerWidget* _trackContainer;
+};
+
+} // namespace Mobile
+} // namespace UserInterface
diff --git a/src/ui/audio/mobile/MobilePlayQueueEvents.hpp b/src/ui/audio/mobile/MobilePlayQueueEvents.hpp
index 9fb80bf4..61775642 100644
--- a/src/ui/audio/mobile/MobilePlayQueueEvents.hpp
+++ b/src/ui/audio/mobile/MobilePlayQueueEvents.hpp
@@ -28,6 +28,7 @@ namespace Mobile {
struct PlayQueueEvents
{
Wt::Signal trackPlay;
+ Wt::Signal trackAdd;
};
diff --git a/src/ui/audio/mobile/ReleaseView.cpp b/src/ui/audio/mobile/ReleaseView.cpp
index a7342f8d..27a6350c 100644
--- a/src/ui/audio/mobile/ReleaseView.cpp
+++ b/src/ui/audio/mobile/ReleaseView.cpp
@@ -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("", 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("", Wt::XHTMLText);
+ trackRes->bindWidget("add-btn", addBtn);
+ addBtn->setStyleClass("mobile-btn");
+ addBtn->clicked().connect(std::bind([=] {
+ _events.trackAdd.emit(track.id());
+ }));
+
+
+
+
_nbTracks++;
}
diff --git a/src/ui/audio/mobile/TrackSearch.cpp b/src/ui/audio/mobile/TrackSearch.cpp
index 504ca3b9..79a2db33 100644
--- a/src/ui/audio/mobile/TrackSearch.cpp
+++ b/src/ui/audio/mobile/TrackSearch.cpp
@@ -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("", 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("", Wt::XHTMLText);
+ res->bindWidget("add-btn", addBtn);
+ addBtn->setStyleClass("mobile-btn");
+ addBtn->clicked().connect(std::bind([=] {
+ _events.trackAdd.emit(track.id());
+ }));
+
+
}
if (moreResults)