From d0e472028d196877ff28da3d0d746082e1766269 Mon Sep 17 00:00:00 2001 From: emeric Date: Tue, 4 Nov 2014 20:37:19 +0100 Subject: [PATCH] WIP. PlayQueue --- src/Makefile.am | 11 +- src/ui/audio/Audio.cpp | 95 ++++++++++--- src/ui/audio/Audio.hpp | 2 + src/ui/audio/PlayQueue.cpp | 132 ++++++++++++++++++ src/ui/audio/PlayQueue.hpp | 67 +++++++++ src/ui/audio/TableFilter.cpp | 2 +- src/ui/audio/TrackView.cpp | 50 ++++--- src/ui/audio/TrackView.hpp | 14 +- .../AvConvTranscodeStreamResource.cpp | 1 - 9 files changed, 322 insertions(+), 52 deletions(-) create mode 100644 src/ui/audio/PlayQueue.cpp create mode 100644 src/ui/audio/PlayQueue.hpp diff --git a/src/Makefile.am b/src/Makefile.am index faaeb570..478d69e7 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -12,14 +12,14 @@ lms_SOURCES = \ $(srcdir)/cover/CoverArt.cpp \ $(srcdir)/cover/CoverArtGrabber.cpp \ $(srcdir)/database/Artist.cpp \ - $(srcdir)/database/Genre.cpp \ - $(srcdir)/database/Release.cpp \ - $(srcdir)/database/MediaDirectory.cpp \ - $(srcdir)/database/Track.cpp \ $(srcdir)/database/DatabaseHandler.cpp \ + $(srcdir)/database/Genre.cpp \ + $(srcdir)/database/MediaDirectory.cpp \ + $(srcdir)/database/Release.cpp \ $(srcdir)/database/SqlQuery.cpp \ - $(srcdir)/database/Video.cpp \ + $(srcdir)/database/Track.cpp \ $(srcdir)/database/User.cpp \ + $(srcdir)/database/Video.cpp \ $(srcdir)/database-updater/DatabaseUpdater.cpp \ $(srcdir)/database-updater/Checksum.cpp \ $(srcdir)/logger/Logger.cpp \ @@ -46,6 +46,7 @@ lms_SOURCES = \ $(srcdir)/ui/audio/AudioMediaPlayer.cpp \ $(srcdir)/ui/audio/FilterChain.cpp \ $(srcdir)/ui/audio/KeywordSearchFilter.cpp \ + $(srcdir)/ui/audio/PlayQueue.cpp \ $(srcdir)/ui/audio/TableFilter.cpp \ $(srcdir)/ui/audio/TrackView.cpp \ $(srcdir)/ui/common/DirectoryValidator.cpp \ diff --git a/src/ui/audio/Audio.cpp b/src/ui/audio/Audio.cpp index c4d67d44..02623358 100644 --- a/src/ui/audio/Audio.cpp +++ b/src/ui/audio/Audio.cpp @@ -30,6 +30,7 @@ #include "TableFilter.hpp" #include "KeywordSearchFilter.hpp" #include "TrackView.hpp" +#include "PlayQueue.hpp" #include "Audio.hpp" @@ -66,33 +67,49 @@ _mediaPlayer(nullptr) mainLayout->addLayout(filterLayout, 0, 0); // ENDOF(Filters) + // TODO ADD controls (Play, add) + // TODO ADD some stats (nb files, total duration, etc.) + + Wt::WVBoxLayout* trackLayout = new Wt::WVBoxLayout(); + TrackView* trackView = new TrackView(_db); - mainLayout->addWidget( trackView, 1, 0); + trackLayout->addWidget(trackView, 1); + + Wt::WHBoxLayout* trackControls = new Wt::WHBoxLayout(); + + Wt::WPushButton* playBtn = new Wt::WPushButton("Play"); + trackControls->addWidget(playBtn); + + Wt::WPushButton* addBtn = new Wt::WPushButton("Add"); + trackControls->addWidget(addBtn); + trackControls->addWidget(new Wt::WText("Total duration: "), 1); + + + trackLayout->addLayout(trackControls); + + mainLayout->addLayout(trackLayout, 1, 0); + _filterChain.addFilter(trackView); - // TODO Playlist here + PlayQueue *playQueue = new PlayQueue(_db); + + // Playlist/PlayQueue { - Wt::WVBoxLayout* playlist = new Wt::WVBoxLayout(); + Wt::Dbo::Transaction transaction(_db.getSession()); + Database::User::pointer user = _db.getCurrentUser(); + + Wt::WVBoxLayout* playQueueLayout = new Wt::WVBoxLayout(); Wt::WHBoxLayout* playlistControls = new Wt::WHBoxLayout(); - Wt::WComboBox *cb = new Wt::WComboBox(); - cb->addItem("metal"); - cb->addItem("rock"); - cb->addItem("top50"); - cb->setWidth(75); + playlistControls->addWidget(new Wt::WPushButton("Playlist")); + playlistControls->addWidget(new Wt::WText("Duration:")); - playlistControls->addWidget(cb, 1); - playlistControls->addWidget(new Wt::WPushButton("Rename")); - playlistControls->addWidget(new Wt::WPushButton("+")); - playlistControls->addWidget(new Wt::WPushButton("-")); + playQueueLayout->addLayout(playlistControls); - playlist->addLayout(playlistControls); + playQueueLayout->addWidget( playQueue, 1); - // TODO - playlist->addWidget( new Wt::WTableView(), 1); - - mainLayout->addLayout(playlist, 0, 1, 2, 1); + mainLayout->addLayout(playQueueLayout, 0, 1, 2, 1); } _mediaPlayer = new AudioMediaPlayer(); @@ -102,13 +119,46 @@ _mediaPlayer(nullptr) mainLayout->setRowResizable(0, true, Wt::WLength(200, Wt::WLength::Pixel)); mainLayout->setColumnResizable(0, true); - trackView->trackSelected().connect(boost::bind(&Audio::playTrack, this, _1)); - - _mediaPlayer->playbackEnded().connect(std::bind([=] () + // Double click on track + // Set the selected tracks to the play queue + trackView->trackDoubleClicked().connect(std::bind([=] () { - // TODO link to playlist - trackView->selectNextTrack(); + std::vector trackIds; + trackView->getSelectedTracks(trackIds); + + playQueue->clear(); + playQueue->addTracks(trackIds); + + playQueue->play(); + })); + + // Play button + // Set the selected tracks to the play queue + playBtn->clicked().connect(std::bind([=] () + { + std::vector trackIds; + trackView->getSelectedTracks(trackIds); + + playQueue->clear(); + playQueue->addTracks(trackIds); + + playQueue->play(); + })); + + // Add Button + // Add the selected tracks at the end of the play queue + addBtn->clicked().connect(std::bind([=] () + { + std::vector trackIds; + trackView->getSelectedTracks(trackIds); + + playQueue->addTracks(trackIds); + })); + + playQueue->playTrack().connect(this, &Audio::playTrack); + + _mediaPlayer->playbackEnded().connect(playQueue, &PlayQueue::handlePlaybackComplete); } void @@ -152,5 +202,6 @@ Audio::playTrack(boost::filesystem::path p) } } + } // namespace UserInterface diff --git a/src/ui/audio/Audio.hpp b/src/ui/audio/Audio.hpp index 39721699..757be352 100644 --- a/src/ui/audio/Audio.hpp +++ b/src/ui/audio/Audio.hpp @@ -45,6 +45,8 @@ class Audio : public Wt::WContainerWidget void playTrack(boost::filesystem::path p); + void handlePlaylistSelected(Wt::WString name); + Database::Handler& _db; AudioMediaPlayer* _mediaPlayer; diff --git a/src/ui/audio/PlayQueue.cpp b/src/ui/audio/PlayQueue.cpp new file mode 100644 index 00000000..76ea1907 --- /dev/null +++ b/src/ui/audio/PlayQueue.cpp @@ -0,0 +1,132 @@ +/* + * Copyright (C) 2014 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 "PlayQueue.hpp" + +namespace UserInterface { + + +PlayQueue::PlayQueue(Database::Handler& db, Wt::WContainerWidget* parent) +: Wt::WTableView(parent), +_db(db), +_playingTrackId(0) +{ + _model = new Wt::WStandardItemModel(0, 4, this); + + // 0 Column is hidden (track id) + _model->setHeaderData(0, Wt::WString("#")); + _model->setHeaderData(1, Wt::WString("#")); + _model->setHeaderData(2, Wt::WString("Track")); + _model->setHeaderData(3, Wt::WString("Duration")); + + this->setModel(_model); + this->setSelectionMode(Wt::ExtendedSelection); + this->setSortingEnabled(false); + this->setAlternatingRowColors(true); + + this->setColumnWidth(0, 50); + this->setColumnWidth(1, 50); + + this->setColumnHidden(0, true); + + this->doubleClicked().connect( std::bind([=] (Wt::WModelIndex idx, Wt::WMouseEvent evt) + { + if (!idx.isValid()) + return; + + // Update selection + Wt::WModelIndexSet indexSet; + indexSet.insert(idx); + + this->setSelectedIndexes( indexSet ); + + // Read the requested track + readTrack(idx.row()); + + }, std::placeholders::_1, std::placeholders::_2)); + +} + +void +PlayQueue::play(void) +{ + readTrack(0); +} + +void +PlayQueue::addTracks(const std::vector& trackIds) +{ + // Add tracks to model + Wt::Dbo::Transaction transaction(_db.getSession()); + + BOOST_FOREACH(Database::Track::id_type trackId, trackIds) + { + Database::Track::pointer track (Database::Track::getById(_db.getSession(), trackId)); + + if (track) + { + int dataRow = _model->rowCount(); + + _model->insertRows(dataRow, 1); + + _model->setData(dataRow, 0, track.id()); + _model->setData(dataRow, 1, dataRow + 1); + _model->setData(dataRow, 2, track->getName()); + _model->setData(dataRow, 3, track->getDuration()); + } + } +} + +void +PlayQueue::clear(void) +{ + _model->removeRows(0, _model->rowCount()); +} + +void +PlayQueue::handlePlaybackComplete(void) +{ + // Play the next track + readTrack(_playingTrackId + 1); +} + +void +PlayQueue::readTrack(int rowId) +{ + Wt::Dbo::Transaction transaction(_db.getSession()); + + while (rowId < _model->rowCount()) + { + Database::Track::id_type trackId = boost::any_cast(_model->data(rowId, 0)); + + Database::Track::pointer track = Database::Track::getById(_db.getSession(), trackId); + if (track) + { + _playingTrackId = rowId; + _sigTrackPlay.emit(track->getPath()); + break; + } + + ++rowId; + } +} + + +} // namespace UserInterface + diff --git a/src/ui/audio/PlayQueue.hpp b/src/ui/audio/PlayQueue.hpp new file mode 100644 index 00000000..93db861f --- /dev/null +++ b/src/ui/audio/PlayQueue.hpp @@ -0,0 +1,67 @@ +/* + * Copyright (C) 2014 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 . + */ + +#ifndef PLAY_QUEUE_HPP +#define PLAY_QUEUE_HPP + +#include +#include + +#include "database/DatabaseHandler.hpp" +#include "database/AudioTypes.hpp" + +namespace UserInterface { + +class PlayQueue : public Wt::WTableView +{ + public: + PlayQueue(Database::Handler& db, Wt::WContainerWidget* parent = 0); + + void addTracks(const std::vector& trackIds); + + void clear(void); + + void play(void); + + // Signals + Wt::Signal< boost::filesystem::path >& playTrack() { return _sigTrackPlay; } + + // Slots + void saveToPlaylist(std::string& playListName); + void loadFromPlaylist(std::string& playListName); + void handlePlaybackComplete(void); + + private: + + void readTrack(int rowId); + + Wt::Signal< boost::filesystem::path > _sigTrackPlay; + + Database::Handler& _db; + + Wt::WStandardItemModel* _model; + + int _playingTrackId; + +}; + + +} // namespace UserInterface + +#endif diff --git a/src/ui/audio/TableFilter.cpp b/src/ui/audio/TableFilter.cpp index 1e802ef3..9bfd10a2 100644 --- a/src/ui/audio/TableFilter.cpp +++ b/src/ui/audio/TableFilter.cpp @@ -105,7 +105,7 @@ TableFilter::getConstraint(Constraint& constraint) if (!index.isValid()) continue; - ResultType result = _queryModel.resultRow( index.row() ); + const ResultType& result = _queryModel.resultRow( index.row() ); // Get the track part std::string name( result.get<0>() ); diff --git a/src/ui/audio/TrackView.cpp b/src/ui/audio/TrackView.cpp index fc45a8bf..a19ef372 100644 --- a/src/ui/audio/TrackView.cpp +++ b/src/ui/audio/TrackView.cpp @@ -44,10 +44,10 @@ _db(db) _queryModel.addColumn( "track.original_date", "Original Date" ); _queryModel.addColumn( "track.genre_list", "Genres" ); - _queryModel.setBatchSize(250); + _queryModel.setBatchSize(500); this->setSortingEnabled(true); - this->setSelectionMode(Wt::SingleSelection); + this->setSelectionMode(Wt::ExtendedSelection); this->setAlternatingRowColors(true); this->setModel(&_queryModel); @@ -83,8 +83,21 @@ _db(db) this->setItemDelegateForColumn(7, delegate); } - // TODO other event! - this->selectionChanged().connect(this, &TrackView::handleTrackSelected); + // If an item is double clicked, select the track and emit signal + this->doubleClicked().connect( std::bind([=] (Wt::WModelIndex idx, Wt::WMouseEvent evt) + { + if (!idx.isValid()) + return; + + Wt::WModelIndexSet indexSet; + indexSet.insert(idx); + + this->setSelectedIndexes( indexSet ); + + _sigTrackDoubleClicked.emit( ); + + }, std::placeholders::_1, std::placeholders::_2)); + } // Set constraints created by parent filters @@ -113,6 +126,7 @@ TrackView::refresh(const Constraint& constraint) } +/* void TrackView::handleTrackSelected(void) { @@ -133,26 +147,28 @@ TrackView::handleTrackSelected(void) } } - +*/ void -TrackView::selectNextTrack(void) +TrackView::getSelectedTracks(std::vector& track_ids) { + LMS_LOG(MOD_UI, SEV_DEBUG) << "Getting selected tracks..."; + Wt::WModelIndexSet indexSet = this->selectedIndexes(); - if (!indexSet.empty()) { - Wt::WModelIndex currentIndex( *indexSet.begin() ); - // Check there are remainin tracks! - if (currentIndex.isValid() && this->model()->rowCount() > currentIndex.row() + 1) - { - this->select( this->model()->index( currentIndex.row() + 1, currentIndex.column())); - ResultType result = _queryModel.resultRow( currentIndex.row() + 1 ); + BOOST_FOREACH(Wt::WModelIndex index, indexSet) + { + if (!index.isValid()) + continue; - // Get the track part - Wt::Dbo::ptr track ( result.get<0>() ); + const ResultType& result = _queryModel.resultRow( index.row() ); - _trackSelected.emit( track->getPath() ); - } + // Get the track part + Wt::Dbo::ptr track ( result.get<0>() ); + + track_ids.push_back(track.id()); } + + LMS_LOG(MOD_UI, SEV_DEBUG) << "Getting all selected tracks DONE..."; } } // namespace UserInterface diff --git a/src/ui/audio/TrackView.hpp b/src/ui/audio/TrackView.hpp index 226968af..fc662dff 100644 --- a/src/ui/audio/TrackView.hpp +++ b/src/ui/audio/TrackView.hpp @@ -36,22 +36,24 @@ class TrackView : public Wt::WTableView, public Filter TrackView( Database::Handler& db, Wt::WContainerWidget* parent = 0); + // Filter interface // Set constraints created by parent filters void refresh(const Constraint& constraint); // Create constraints for child filters (N/A) void getConstraint(Constraint& constraint) {} - void selectNextTrack(void); // Emit a trackSelected() if success - // Signals - Wt::Signal< boost::filesystem::path >& trackSelected() { return _trackSelected; } + // Get all the tracks that are currently displayed + void getSelectedTracks(std::vector& track_ids); + + typedef Wt::Signal SigTrackDoubleClicked; + + SigTrackDoubleClicked& trackDoubleClicked() { return _sigTrackDoubleClicked; } private: - void handleTrackSelected(); - - Wt::Signal< boost::filesystem::path > _trackSelected; + SigTrackDoubleClicked _sigTrackDoubleClicked; Database::Handler& _db; diff --git a/src/ui/resource/AvConvTranscodeStreamResource.cpp b/src/ui/resource/AvConvTranscodeStreamResource.cpp index 55df2c00..53acf85a 100644 --- a/src/ui/resource/AvConvTranscodeStreamResource.cpp +++ b/src/ui/resource/AvConvTranscodeStreamResource.cpp @@ -78,7 +78,6 @@ AvConvTranscodeStreamResource::handleRequest(const Wt::Http::Request& request, if (!transcoder->isComplete() && response.out()) { continuation = response.createContinuation(); continuation->setData(transcoder); - LMS_LOG(MOD_UI, SEV_DEBUG) << "Continuation set!"; } else LMS_LOG(MOD_UI, SEV_DEBUG) << "No more data!";