[UI] Playqueue: added loop and shuffle

This commit is contained in:
emeric
2014-12-04 13:08:08 +01:00
parent d695a544b6
commit cbf10b62d0
5 changed files with 328 additions and 91 deletions
+45 -36
View File
@@ -175,6 +175,8 @@ _playQueue(nullptr)
_mediaPlayer->playbackEnded().connect(_playQueue, &PlayQueue::handlePlaybackComplete); _mediaPlayer->playbackEnded().connect(_playQueue, &PlayQueue::handlePlaybackComplete);
_mediaPlayer->playNext().connect(_playQueue, &PlayQueue::playNext); _mediaPlayer->playNext().connect(_playQueue, &PlayQueue::playNext);
_mediaPlayer->playPrevious().connect(_playQueue, &PlayQueue::playPrevious); _mediaPlayer->playPrevious().connect(_playQueue, &PlayQueue::playPrevious);
_mediaPlayer->shuffle().connect(boost::bind(&PlayQueue::setShuffle, _playQueue, _1));
_mediaPlayer->loop().connect(boost::bind(&PlayQueue::setLoop,_playQueue, _1));
} }
void void
@@ -183,42 +185,6 @@ Audio::search(const std::string& searchText)
_filterChain.searchKeyword(searchText); _filterChain.searchKeyword(searchText);
} }
void
Audio::playSelectedTracks(PlayQueueAddType addType)
{
int firstTrackPos = 0;
std::vector<Database::Track::id_type> trackIds;
switch(addType)
{
case PlayQueueAddAllTracks:
// Play all the tracks, from the first one
firstTrackPos = 0;
_trackView->getTracks(trackIds);
break;
case PlayQueueAddSelectedTracks:
// If nothing or only ONE selected, get them all
if (_trackView->getNbSelectedTracks() <= 1)
{
// Start to play at the selected track, if any
firstTrackPos = _trackView->getFirstSelectedTrackPosition();
_trackView->getTracks(trackIds);
}
else
{
// Play all the selected tracks, frm the first one
firstTrackPos = 0;
_trackView->getSelectedTracks(trackIds);
}
break;
}
_playQueue->clear();
_playQueue->addTracks(trackIds);
_playQueue->play(firstTrackPos);
}
void void
Audio::addSelectedTracks(void) Audio::addSelectedTracks(void)
{ {
@@ -232,6 +198,49 @@ Audio::addSelectedTracks(void)
_playQueue->addTracks(trackIds); _playQueue->addTracks(trackIds);
} }
void
Audio::playSelectedTracks(PlayQueueAddType addType)
{
std::vector<Database::Track::id_type> trackIds;
_playQueue->clear();
switch(addType)
{
case PlayQueueAddAllTracks:
// Play all the tracks
_trackView->getTracks(trackIds);
_playQueue->addTracks(trackIds);
_playQueue->play();
break;
case PlayQueueAddSelectedTracks:
// If nothing selected, get all the track and play everything
if (_trackView->getNbSelectedTracks() == 0)
{
_trackView->getTracks(trackIds);
_playQueue->addTracks(trackIds);
_playQueue->play();
}
// If only ONE selected, get them all and pre select the track
else if (_trackView->getNbSelectedTracks() == 1)
{
// Start to play at the selected track, if any
_trackView->getTracks(trackIds);
_playQueue->addTracks(trackIds);
_playQueue->play(_trackView->getFirstSelectedTrackPosition());
}
else
{
// Play all the selected tracks
_trackView->getSelectedTracks(trackIds);
_playQueue->addTracks(trackIds);
_playQueue->play();
}
break;
}
}
void void
Audio::playTrack(boost::filesystem::path p) Audio::playTrack(boost::filesystem::path p)
{ {
+13
View File
@@ -20,6 +20,7 @@
#include <Wt/WMediaPlayer> #include <Wt/WMediaPlayer>
#include <Wt/WProgressBar> #include <Wt/WProgressBar>
#include <Wt/WCheckBox>
#include "AudioMediaPlayer.hpp" #include "AudioMediaPlayer.hpp"
@@ -39,6 +40,18 @@ AudioMediaPlayer::AudioMediaPlayer( Wt::WContainerWidget *parent)
{ {
Wt::WContainerWidget *container = new Wt::WContainerWidget(this); Wt::WContainerWidget *container = new Wt::WContainerWidget(this);
Wt::WCheckBox *loop = new Wt::WCheckBox("Loop", this);
loop->changed().connect(std::bind([=] ()
{
_loop.emit( loop->checkState() == Wt::Checked);
}));
Wt::WCheckBox *shuffle = new Wt::WCheckBox("Shuffle", this);
shuffle->changed().connect(std::bind([=] ()
{
_shuffle.emit( shuffle->checkState() == Wt::Checked);
}));
Wt::WPushButton *prevBtn = new Wt::WPushButton("<<", container ); Wt::WPushButton *prevBtn = new Wt::WPushButton("<<", container );
_playBtn = new Wt::WPushButton("Play", container ); _playBtn = new Wt::WPushButton("Play", container );
_pauseBtn = new Wt::WPushButton("Pause", container ); _pauseBtn = new Wt::WPushButton("Pause", container );
+5 -3
View File
@@ -42,12 +42,12 @@ class AudioMediaPlayer : public Wt::WContainerWidget
void load(const Transcode::Parameters& parameters); void load(const Transcode::Parameters& parameters);
// Signal slot Ended // Signal slots
Wt::Signal<void>& playbackEnded() {return _playbackEnded;} Wt::Signal<void>& playbackEnded() {return _playbackEnded;}
// Signal slot Next
Wt::Signal<void>& playNext() {return _playNext;} Wt::Signal<void>& playNext() {return _playNext;}
// Signal Slot Previous
Wt::Signal<void>& playPrevious() {return _playPrevious;} Wt::Signal<void>& playPrevious() {return _playPrevious;}
Wt::Signal<bool>& shuffle() {return _shuffle;}
Wt::Signal<bool>& loop() {return _loop;}
private: private:
@@ -66,6 +66,8 @@ class AudioMediaPlayer : public Wt::WContainerWidget
Wt::Signal<void> _playbackEnded; Wt::Signal<void> _playbackEnded;
Wt::Signal<void> _playNext; Wt::Signal<void> _playNext;
Wt::Signal<void> _playPrevious; Wt::Signal<void> _playPrevious;
Wt::Signal<bool> _shuffle;
Wt::Signal<bool> _loop;
// Core // Core
Wt::WMediaPlayer* _mediaPlayer; Wt::WMediaPlayer* _mediaPlayer;
+255 -48
View File
@@ -17,6 +17,9 @@
* along with LMS. If not, see <http://www.gnu.org/licenses/>. * along with LMS. If not, see <http://www.gnu.org/licenses/>.
*/ */
#include <boost/random/mersenne_twister.hpp>
#include <boost/random/uniform_int_distribution.hpp>
#include <Wt/WItemDelegate> #include <Wt/WItemDelegate>
#include "logger/Logger.hpp" #include "logger/Logger.hpp"
@@ -41,32 +44,184 @@ namespace {
model->setData(row2, i, tmp); // tmp-> 2 model->setData(row2, i, tmp); // tmp-> 2
} }
} }
} }
namespace UserInterface { namespace UserInterface {
static const int trackIdInvalid = -1; static const int trackPosInvalid = -1;
class TrackSelector
{
public:
TrackSelector();
void setShuffle(bool enable);
void setLoop(bool enable) { _loop = enable; }
int getPrevious(void);
int getNext(void);
int getCurrent(void);
// Set the internal pos thanks to the track pos
void setPosByRowId(int rowId);
void setPos(int pos) { _curPos = pos; }
void setSize(std::size_t size);
std::size_t getSize(void) const { return _size;}
private:
void refreshPositions();
bool _loop;
bool _shuffle;
std::size_t _size;
std::size_t _curPos;
std::vector<int> _trackPos;
};
TrackSelector::TrackSelector()
: _loop(false),
_shuffle(false),
_curPos(0)
{
}
void
TrackSelector::refreshPositions()
{
_trackPos.clear();
if (_size == 0)
return;
for (std::size_t i = 0; i < _size; ++i)
_trackPos.push_back(i);
// Now shuffle
// Perform size/2 permutations
boost::random::mt19937 rng; // produces randomness out of thin air
rng.seed(static_cast<unsigned int>(std::time(0)));
for (std::size_t i = 0; i < _size; ++i)
{
boost::random::uniform_int_distribution<> distribution(i, _size - 1);
int val1 = distribution(rng);
int tmp = _trackPos[i];
_trackPos[i] = _trackPos[val1];
_trackPos[val1] = tmp;
}
}
int
TrackSelector::getCurrent()
{
return _shuffle ? _trackPos[_curPos] : _curPos;
}
void
TrackSelector::setShuffle(bool enable)
{
_shuffle = enable;
if (_shuffle)
setPosByRowId(_curPos);
else
setPos(_trackPos[_curPos]);
}
int
TrackSelector::getNext()
{
if (_size == 0)
return trackPosInvalid;
if (_curPos == _size - 1)
{
if (!_loop)
return trackPosInvalid;
else
_curPos = 0;
}
else
++_curPos;
return _shuffle ? _trackPos[_curPos] : _curPos;
}
int
TrackSelector::getPrevious()
{
if (_size == 0)
return trackPosInvalid;
if (_curPos == 0)
{
if (!_loop)
return trackPosInvalid;
else
_curPos = _size - 1;
}
else
--_curPos;
return _shuffle ? _trackPos[_curPos] : _curPos;
}
void
TrackSelector::setPosByRowId(int rowId)
{
if (_shuffle)
{
for (std::size_t i = 0; i < _trackPos.size(); ++i)
{
if (rowId == _trackPos[i])
{
_curPos = i;
break;
}
}
}
else
_curPos = rowId;
}
void
TrackSelector::setSize(std::size_t size)
{
_size = size;
_curPos = 0;
refreshPositions();
}
class PlayQueueItemDelegate : public Wt::WItemDelegate class PlayQueueItemDelegate : public Wt::WItemDelegate
{ {
public: public:
PlayQueueItemDelegate(Wt::WObject *parent = 0) : Wt::WItemDelegate(parent), _selectedRowId(trackIdInvalid) {} PlayQueueItemDelegate(Wt::WObject *parent = 0) : Wt::WItemDelegate(parent), _selectedRowPos(trackPosInvalid) {}
void setSelectedRowId(int rowId) { _selectedRowId = rowId; } void setSelectedRowPos(int rowPos) { _selectedRowPos = rowPos; }
Wt::WWidget* update(Wt::WWidget *widget, const Wt::WModelIndex &index, Wt::WFlags< Wt::ViewItemRenderFlag > flags) Wt::WWidget* update(Wt::WWidget *widget, const Wt::WModelIndex &index, Wt::WFlags< Wt::ViewItemRenderFlag > flags)
{ {
Wt::WWidget* res = Wt::WItemDelegate::update(widget, index, flags); Wt::WWidget* res = Wt::WItemDelegate::update(widget, index, flags);
if (res && index.isValid() && index.row() == _selectedRowId) if (res && index.isValid())
res->toggleStyleClass("playqueue-playing", true); {
if (_selectedRowPos != trackPosInvalid && index.row() == _selectedRowPos)
res->toggleStyleClass("playqueue-playing", true);
else
res->toggleStyleClass("playqueue-playing", false);
}
return res; return res;
} }
private: private:
int _selectedRowId; int _selectedRowPos;
}; };
@@ -74,7 +229,8 @@ class PlayQueueItemDelegate : public Wt::WItemDelegate
PlayQueue::PlayQueue(Database::Handler& db, Wt::WContainerWidget* parent) PlayQueue::PlayQueue(Database::Handler& db, Wt::WContainerWidget* parent)
: Wt::WTableView(parent), : Wt::WTableView(parent),
_db(db), _db(db),
_playedId(trackIdInvalid) _curPlayedTrackPos(trackPosInvalid),
_trackSelector(new TrackSelector())
{ {
_model = new Wt::WStandardItemModel(0, 4, this); _model = new Wt::WStandardItemModel(0, 4, this);
@@ -109,16 +265,41 @@ _playedId(trackIdInvalid)
this->setSelectedIndexes( indexSet ); this->setSelectedIndexes( indexSet );
// Read the requested track // Read the requested track
readTrack(idx.row()); play(idx.row());
}, std::placeholders::_1, std::placeholders::_2)); }, std::placeholders::_1, std::placeholders::_2));
} }
void void
PlayQueue::play(int trackId) PlayQueue::setShuffle(bool enable)
{ {
readTrack(trackId); _trackSelector->setShuffle(enable);
}
void
PlayQueue::setLoop(bool enable)
{
_trackSelector->setLoop(enable);
}
void
PlayQueue::play()
{
_trackSelector->setPos(0);
if (!readTrack(_trackSelector->getCurrent()))
playNext();
}
void
PlayQueue::play(int rowId)
{
// Update the track selector to use the requested track as current position
_trackSelector->setPosByRowId(rowId);
if (!readTrack(_trackSelector->getCurrent()))
playNext();
} }
void void
@@ -143,6 +324,8 @@ PlayQueue::addTracks(const std::vector<Database::Track::id_type>& trackIds)
_model->setData(dataRow, 3, track->getDuration()); _model->setData(dataRow, 3, track->getDuration());
} }
} }
_trackSelector->setSize( _model->rowCount() );
} }
void void
@@ -151,68 +334,88 @@ PlayQueue::clear(void)
_model->removeRows(0, _model->rowCount()); _model->removeRows(0, _model->rowCount());
// Reset play id // Reset play id
_playedId = trackIdInvalid; _curPlayedTrackPos = trackPosInvalid;
_trackSelector->setSize( 0 );
} }
void void
PlayQueue::handlePlaybackComplete(void) PlayQueue::handlePlaybackComplete(void)
{ {
// Play the next track playNext();
if (_playedId != trackIdInvalid)
readTrack(_playedId + 1);
} }
void void
PlayQueue::playNext(void) PlayQueue::playNext(void)
{ {
readTrack( _playedId != trackIdInvalid ? _playedId + 1 : 0); std::size_t nbTries = _trackSelector->getSize();
while (nbTries > 0)
{
int pos = _trackSelector->getNext();
if (pos == trackPosInvalid)
break;
if (readTrack(pos))
break;
--nbTries;
}
} }
void void
PlayQueue::playPrevious(void) PlayQueue::playPrevious(void)
{ {
readTrack( _playedId >= 1 ? _playedId - 1 : 0); std::size_t nbTries = _trackSelector->getSize();
while (nbTries > 0)
{
int pos = _trackSelector->getPrevious();
if (pos == trackPosInvalid)
break;
if (readTrack(pos))
break;
--nbTries;
}
} }
void bool
PlayQueue::readTrack(int rowId) PlayQueue::readTrack(int rowPos)
{ {
Wt::Dbo::Transaction transaction(_db.getSession()); Wt::Dbo::Transaction transaction(_db.getSession());
while (rowId < _model->rowCount()) LMS_LOG(MOD_UI, SEV_DEBUG) << "Reading track at pos " << rowPos;
Database::Track::id_type trackId = boost::any_cast<Database::Track::id_type>(_model->data(rowPos, 0));
Database::Track::pointer track = Database::Track::getById(_db.getSession(), trackId);
if (track)
{ {
Database::Track::id_type trackId = boost::any_cast<Database::Track::id_type>(_model->data(rowId, 0)); setPlayingTrackPos(rowPos);
Database::Track::pointer track = Database::Track::getById(_db.getSession(), trackId); _sigTrackPlay.emit(track->getPath());
if (track)
{
setPlayingTrackId(rowId);
_sigTrackPlay.emit(track->getPath()); return true;
return;
}
++rowId;
} }
else
return false;
} }
void void
PlayQueue::setPlayingTrackId(int newRowId) PlayQueue::setPlayingTrackPos(int newRowPos)
{ {
int oldRowId = _playedId; int oldRowPos = _curPlayedTrackPos;
_playedId = newRowId; _curPlayedTrackPos = newRowPos;
_itemDelegate->setSelectedRowId(newRowId); _itemDelegate->setSelectedRowPos(newRowPos);
// Hack re-set the data in order to trigger the rerending of the widget // Hack re-set the data in order to trigger the rerending of the widget
// calling the update method of our custom item delegate gives bad results // calling the update method of our custom item delegate gives bad results
if (oldRowId >= 0) if (oldRowPos >= 0)
modelForceRefreshDataRow(_model, oldRowId); modelForceRefreshDataRow(_model, oldRowPos);
if (newRowId >= 0) if (newRowPos >= 0)
modelForceRefreshDataRow(_model, newRowId); modelForceRefreshDataRow(_model, newRowPos);
} }
void void
@@ -229,9 +432,11 @@ PlayQueue::delSelected(void)
} }
// If the current played track is removed, make sure to unselect it // If the current played track is removed, make sure to unselect it
_itemDelegate->setSelectedRowId(trackIdInvalid); _itemDelegate->setSelectedRowPos(trackPosInvalid);
_trackSelector->setSize(_model->rowCount());
renumber(minId, _model->rowCount() - 1); renumber(minId, _model->rowCount() - 1);
} }
void void
@@ -260,14 +465,15 @@ PlayQueue::moveSelectedUp(void)
maxId = index.row(); maxId = index.row();
// Update playing status // Update playing status
if (_playedId == index.row()) if (_curPlayedTrackPos == index.row())
setPlayingTrackId(_playedId - 1); setPlayingTrackPos(_curPlayedTrackPos - 1);
else if (_playedId == index.row() - 1) else if (_curPlayedTrackPos == index.row() - 1)
setPlayingTrackId(_playedId + 1); setPlayingTrackPos(_curPlayedTrackPos + 1);
newIndexSet.insert( _model->index(index.row() - 1, 0)); newIndexSet.insert( _model->index(index.row() - 1, 0));
} }
_trackSelector->setPosByRowId(_curPlayedTrackPos);
this->setSelectedIndexes( newIndexSet ); this->setSelectedIndexes( newIndexSet );
renumber(minId, maxId); renumber(minId, maxId);
@@ -300,14 +506,15 @@ PlayQueue::moveSelectedDown(void)
maxId = index.row() + 1; maxId = index.row() + 1;
// Update playing status // Update playing status
if (_playedId == index.row()) if (_curPlayedTrackPos == index.row())
setPlayingTrackId(_playedId + 1); setPlayingTrackPos(_curPlayedTrackPos + 1);
else if (_playedId == index.row() + 1) else if (_curPlayedTrackPos == index.row() + 1)
setPlayingTrackId(_playedId - 1); setPlayingTrackPos(_curPlayedTrackPos - 1);
newIndexSet.insert( _model->index(index.row() + 1, 0)); newIndexSet.insert( _model->index(index.row() + 1, 0));
} }
_trackSelector->setPosByRowId(_curPlayedTrackPos);
this->setSelectedIndexes( newIndexSet ); this->setSelectedIndexes( newIndexSet );
renumber(minId, maxId); renumber(minId, maxId);
+10 -4
View File
@@ -29,6 +29,7 @@
namespace UserInterface { namespace UserInterface {
class PlayQueueItemDelegate; class PlayQueueItemDelegate;
class TrackSelector;
class PlayQueue : public Wt::WTableView class PlayQueue : public Wt::WTableView
{ {
@@ -39,8 +40,12 @@ class PlayQueue : public Wt::WTableView
void clear(void); void clear(void);
void setShuffle(bool enable); // true to enable shuffle
void setLoop(bool enable); // true to enable loop
// Play functions // Play functions
void play(int trackId = 0); // Play the queue void play(void); // Play the queue from the beginning
void play(int rowId); // Play the queue from the given rowId
void playNext(void); // Play the next track void playNext(void); // Play the next track
void playPrevious(void); // Play the previous track void playPrevious(void); // Play the previous track
@@ -59,8 +64,8 @@ class PlayQueue : public Wt::WTableView
private: private:
void readTrack(int rowId); bool readTrack(int rowId);
void setPlayingTrackId(int newRowId); void setPlayingTrackPos(int newRowPos);
void renumber(int firstId, int lastId); void renumber(int firstId, int lastId);
Wt::Signal< boost::filesystem::path > _sigTrackPlay; Wt::Signal< boost::filesystem::path > _sigTrackPlay;
@@ -71,7 +76,8 @@ class PlayQueue : public Wt::WTableView
PlayQueueItemDelegate* _itemDelegate; PlayQueueItemDelegate* _itemDelegate;
int _playedId; int _curPlayedTrackPos;
std::unique_ptr<TrackSelector> _trackSelector;
}; };