From cbf10b62d00599bd723ab484cbddd76722921981 Mon Sep 17 00:00:00 2001 From: emeric Date: Thu, 4 Dec 2014 13:08:08 +0100 Subject: [PATCH] [UI] Playqueue: added loop and shuffle --- src/ui/audio/Audio.cpp | 81 ++++---- src/ui/audio/AudioMediaPlayer.cpp | 13 ++ src/ui/audio/AudioMediaPlayer.hpp | 8 +- src/ui/audio/PlayQueue.cpp | 303 +++++++++++++++++++++++++----- src/ui/audio/PlayQueue.hpp | 14 +- 5 files changed, 328 insertions(+), 91 deletions(-) diff --git a/src/ui/audio/Audio.cpp b/src/ui/audio/Audio.cpp index 5bb9ab17..b26eb648 100644 --- a/src/ui/audio/Audio.cpp +++ b/src/ui/audio/Audio.cpp @@ -175,6 +175,8 @@ _playQueue(nullptr) _mediaPlayer->playbackEnded().connect(_playQueue, &PlayQueue::handlePlaybackComplete); _mediaPlayer->playNext().connect(_playQueue, &PlayQueue::playNext); _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 @@ -183,42 +185,6 @@ Audio::search(const std::string& searchText) _filterChain.searchKeyword(searchText); } -void -Audio::playSelectedTracks(PlayQueueAddType addType) -{ - int firstTrackPos = 0; - std::vector 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 Audio::addSelectedTracks(void) { @@ -232,6 +198,49 @@ Audio::addSelectedTracks(void) _playQueue->addTracks(trackIds); } +void +Audio::playSelectedTracks(PlayQueueAddType addType) +{ + std::vector 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 Audio::playTrack(boost::filesystem::path p) { diff --git a/src/ui/audio/AudioMediaPlayer.cpp b/src/ui/audio/AudioMediaPlayer.cpp index ce91170f..c3308062 100644 --- a/src/ui/audio/AudioMediaPlayer.cpp +++ b/src/ui/audio/AudioMediaPlayer.cpp @@ -20,6 +20,7 @@ #include #include +#include #include "AudioMediaPlayer.hpp" @@ -39,6 +40,18 @@ AudioMediaPlayer::AudioMediaPlayer( Wt::WContainerWidget *parent) { 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 ); _playBtn = new Wt::WPushButton("Play", container ); _pauseBtn = new Wt::WPushButton("Pause", container ); diff --git a/src/ui/audio/AudioMediaPlayer.hpp b/src/ui/audio/AudioMediaPlayer.hpp index 60f34874..30f3a18d 100644 --- a/src/ui/audio/AudioMediaPlayer.hpp +++ b/src/ui/audio/AudioMediaPlayer.hpp @@ -42,12 +42,12 @@ class AudioMediaPlayer : public Wt::WContainerWidget void load(const Transcode::Parameters& parameters); - // Signal slot Ended + // Signal slots Wt::Signal& playbackEnded() {return _playbackEnded;} - // Signal slot Next Wt::Signal& playNext() {return _playNext;} - // Signal Slot Previous Wt::Signal& playPrevious() {return _playPrevious;} + Wt::Signal& shuffle() {return _shuffle;} + Wt::Signal& loop() {return _loop;} private: @@ -66,6 +66,8 @@ class AudioMediaPlayer : public Wt::WContainerWidget Wt::Signal _playbackEnded; Wt::Signal _playNext; Wt::Signal _playPrevious; + Wt::Signal _shuffle; + Wt::Signal _loop; // Core Wt::WMediaPlayer* _mediaPlayer; diff --git a/src/ui/audio/PlayQueue.cpp b/src/ui/audio/PlayQueue.cpp index 3d2b74ff..2183b3c1 100644 --- a/src/ui/audio/PlayQueue.cpp +++ b/src/ui/audio/PlayQueue.cpp @@ -17,6 +17,9 @@ * along with LMS. If not, see . */ +#include +#include + #include #include "logger/Logger.hpp" @@ -41,32 +44,184 @@ namespace { model->setData(row2, i, tmp); // tmp-> 2 } } + } 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 _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(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 { 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* res = Wt::WItemDelegate::update(widget, index, flags); - if (res && index.isValid() && index.row() == _selectedRowId) - res->toggleStyleClass("playqueue-playing", true); + if (res && index.isValid()) + { + if (_selectedRowPos != trackPosInvalid && index.row() == _selectedRowPos) + res->toggleStyleClass("playqueue-playing", true); + else + res->toggleStyleClass("playqueue-playing", false); + } return res; } private: - int _selectedRowId; + int _selectedRowPos; }; @@ -74,7 +229,8 @@ class PlayQueueItemDelegate : public Wt::WItemDelegate PlayQueue::PlayQueue(Database::Handler& db, Wt::WContainerWidget* parent) : Wt::WTableView(parent), _db(db), -_playedId(trackIdInvalid) +_curPlayedTrackPos(trackPosInvalid), +_trackSelector(new TrackSelector()) { _model = new Wt::WStandardItemModel(0, 4, this); @@ -109,16 +265,41 @@ _playedId(trackIdInvalid) this->setSelectedIndexes( indexSet ); // Read the requested track - readTrack(idx.row()); + play(idx.row()); }, std::placeholders::_1, std::placeholders::_2)); } 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 @@ -143,6 +324,8 @@ PlayQueue::addTracks(const std::vector& trackIds) _model->setData(dataRow, 3, track->getDuration()); } } + + _trackSelector->setSize( _model->rowCount() ); } void @@ -151,68 +334,88 @@ PlayQueue::clear(void) _model->removeRows(0, _model->rowCount()); // Reset play id - _playedId = trackIdInvalid; + _curPlayedTrackPos = trackPosInvalid; + _trackSelector->setSize( 0 ); } void PlayQueue::handlePlaybackComplete(void) { - // Play the next track - if (_playedId != trackIdInvalid) - readTrack(_playedId + 1); + 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 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 -PlayQueue::readTrack(int rowId) +bool +PlayQueue::readTrack(int rowPos) { 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(_model->data(rowPos, 0)); + + Database::Track::pointer track = Database::Track::getById(_db.getSession(), trackId); + if (track) { - Database::Track::id_type trackId = boost::any_cast(_model->data(rowId, 0)); + setPlayingTrackPos(rowPos); - Database::Track::pointer track = Database::Track::getById(_db.getSession(), trackId); - if (track) - { - setPlayingTrackId(rowId); + _sigTrackPlay.emit(track->getPath()); - _sigTrackPlay.emit(track->getPath()); - - return; - } - - ++rowId; + return true; } - + else + return false; } void -PlayQueue::setPlayingTrackId(int newRowId) +PlayQueue::setPlayingTrackPos(int newRowPos) { - int oldRowId = _playedId; - _playedId = newRowId; + int oldRowPos = _curPlayedTrackPos; + _curPlayedTrackPos = newRowPos; - _itemDelegate->setSelectedRowId(newRowId); + _itemDelegate->setSelectedRowPos(newRowPos); // 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 - if (oldRowId >= 0) - modelForceRefreshDataRow(_model, oldRowId); + if (oldRowPos >= 0) + modelForceRefreshDataRow(_model, oldRowPos); - if (newRowId >= 0) - modelForceRefreshDataRow(_model, newRowId); + if (newRowPos >= 0) + modelForceRefreshDataRow(_model, newRowPos); } void @@ -229,9 +432,11 @@ PlayQueue::delSelected(void) } // 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); + } void @@ -260,14 +465,15 @@ PlayQueue::moveSelectedUp(void) maxId = index.row(); // Update playing status - if (_playedId == index.row()) - setPlayingTrackId(_playedId - 1); - else if (_playedId == index.row() - 1) - setPlayingTrackId(_playedId + 1); + if (_curPlayedTrackPos == index.row()) + setPlayingTrackPos(_curPlayedTrackPos - 1); + else if (_curPlayedTrackPos == index.row() - 1) + setPlayingTrackPos(_curPlayedTrackPos + 1); newIndexSet.insert( _model->index(index.row() - 1, 0)); } + _trackSelector->setPosByRowId(_curPlayedTrackPos); this->setSelectedIndexes( newIndexSet ); renumber(minId, maxId); @@ -300,14 +506,15 @@ PlayQueue::moveSelectedDown(void) maxId = index.row() + 1; // Update playing status - if (_playedId == index.row()) - setPlayingTrackId(_playedId + 1); - else if (_playedId == index.row() + 1) - setPlayingTrackId(_playedId - 1); + if (_curPlayedTrackPos == index.row()) + setPlayingTrackPos(_curPlayedTrackPos + 1); + else if (_curPlayedTrackPos == index.row() + 1) + setPlayingTrackPos(_curPlayedTrackPos - 1); newIndexSet.insert( _model->index(index.row() + 1, 0)); } + _trackSelector->setPosByRowId(_curPlayedTrackPos); this->setSelectedIndexes( newIndexSet ); renumber(minId, maxId); diff --git a/src/ui/audio/PlayQueue.hpp b/src/ui/audio/PlayQueue.hpp index d73c17c7..18b1d918 100644 --- a/src/ui/audio/PlayQueue.hpp +++ b/src/ui/audio/PlayQueue.hpp @@ -29,6 +29,7 @@ namespace UserInterface { class PlayQueueItemDelegate; +class TrackSelector; class PlayQueue : public Wt::WTableView { @@ -39,8 +40,12 @@ class PlayQueue : public Wt::WTableView void clear(void); + void setShuffle(bool enable); // true to enable shuffle + void setLoop(bool enable); // true to enable loop + // 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 playPrevious(void); // Play the previous track @@ -59,8 +64,8 @@ class PlayQueue : public Wt::WTableView private: - void readTrack(int rowId); - void setPlayingTrackId(int newRowId); + bool readTrack(int rowId); + void setPlayingTrackPos(int newRowPos); void renumber(int firstId, int lastId); Wt::Signal< boost::filesystem::path > _sigTrackPlay; @@ -71,7 +76,8 @@ class PlayQueue : public Wt::WTableView PlayQueueItemDelegate* _itemDelegate; - int _playedId; + int _curPlayedTrackPos; + std::unique_ptr _trackSelector; };