Files
lms/src/ui/audio/desktop/DesktopAudio.cpp
T

546 lines
15 KiB
C++

/*
* Copyright (C) 2013 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 <boost/bind.hpp>
#include <Wt/WVBoxLayout>
#include <Wt/WHBoxLayout>
#include <Wt/WGridLayout>
#include <Wt/WComboBox>
#include <Wt/WPushButton>
#include <Wt/WMessageBox>
#include <Wt/WLengthValidator>
#include <Wt/WDialog>
#include <Wt/WLineEdit>
#include <Wt/WLabel>
#include "logger/Logger.hpp"
#include "LmsApplication.hpp"
#include "TableFilter.hpp"
#include "KeywordSearchFilter.hpp"
#include "DesktopAudio.hpp"
namespace {
void WPopupMenuClear(Wt::WPopupMenu* menu)
{
while(menu->count() > 0)
menu->removeItem(menu->itemAt(0));
}
}
namespace UserInterface {
namespace Desktop {
// Special playlist generated each time the playque gets changed
// Restored at the beginning of the session
static const std::string CurrentQueuePlaylistName = "__current__";
Audio::Audio(Wt::WContainerWidget* parent)
: UserInterface::Audio(parent),
_mediaPlayer(nullptr),
_trackView(nullptr),
_playQueue(nullptr)
{
Wt::WGridLayout *mainLayout = new Wt::WGridLayout();
this->setLayout(mainLayout);
mainLayout->setContentsMargins(9,4,9,9);
// Filters
Wt::WHBoxLayout *filterLayout = new Wt::WHBoxLayout();
TableFilterGenre *filterGenre = new TableFilterGenre();
filterLayout->addWidget(filterGenre);
_filterChain.addFilter(filterGenre);
TableFilterArtist *filterArtist = new TableFilterArtist();
filterLayout->addWidget(filterArtist);
_filterChain.addFilter(filterArtist);
TableFilterRelease *filterRelease = new TableFilterRelease();
filterLayout->addWidget(filterRelease);
_filterChain.addFilter(filterRelease);
mainLayout->addLayout(filterLayout, 0, 1);
// ENDOF(Filters)
// TODO ADD some stats (nb files, total duration, etc.)
Wt::WVBoxLayout* trackLayout = new Wt::WVBoxLayout();
_trackView = new TrackView();
trackLayout->addWidget(_trackView, 1);
Wt::WHBoxLayout* trackControls = new Wt::WHBoxLayout();
Wt::WPushButton* playBtn = new Wt::WPushButton("Play");
playBtn->setStyleClass("btn-sm");
trackControls->addWidget(playBtn);
Wt::WPushButton* addBtn = new Wt::WPushButton("Add");
addBtn->setStyleClass("btn-sm");
trackControls->addWidget(addBtn);
trackControls->addWidget(new Wt::WText("Total duration: "), 1);
trackLayout->addLayout(trackControls);
mainLayout->addLayout(trackLayout, 1, 1);
_filterChain.addFilter(_trackView);
_playQueue = new PlayQueue();
// Playlist/PlayQueue
{
Wt::Dbo::Transaction transaction(DboSession());
Wt::WContainerWidget* playQueueContainer = new Wt::WContainerWidget();
playQueueContainer->setStyleClass("playqueue");
Wt::WVBoxLayout* playQueueLayout = new Wt::WVBoxLayout();
playQueueContainer->setLayout(playQueueLayout);
// Determine the encoding to be used
Wt::WMediaPlayer::Encoding encoding;
switch (CurrentUser()->getAudioEncoding())
{
case Database::AudioEncoding::MP3: encoding = Wt::WMediaPlayer::MP3; break;
case Database::AudioEncoding::WEBMA: encoding = Wt::WMediaPlayer::WEBMA; break;
case Database::AudioEncoding::OGA: encoding = Wt::WMediaPlayer::OGA; break;
case Database::AudioEncoding::FLA: encoding = Wt::WMediaPlayer::FLA; break;
case Database::AudioEncoding::AUTO:
default:
encoding = AudioMediaPlayer::getBestEncoding();
}
LMS_LOG(MOD_UI, SEV_INFO) << "Audio player using encoding " << encoding;
_mediaPlayer = new AudioMediaPlayer(encoding);
playQueueLayout->addWidget(_mediaPlayer);
playQueueLayout->addWidget( _playQueue, 1);
Wt::WHBoxLayout* playlistControls = new Wt::WHBoxLayout();
Wt::WPushButton *playlistBtn = new Wt::WPushButton("Playlist");
playlistBtn->setStyleClass("btn-sm btn-primary");
playlistControls->addWidget(playlistBtn);
// Playlist menu
{
Wt::WPopupMenu *popupMain = new Wt::WPopupMenu();
_popupMenuSave = new Wt::WPopupMenu();
popupMain->addMenu("Save", _popupMenuSave);
_popupMenuLoad = new Wt::WPopupMenu();
popupMain->addMenu("Load", _popupMenuLoad);
_popupMenuDelete = new Wt::WPopupMenu();
popupMain->addMenu("Delete", _popupMenuDelete);
playlistBtn->setMenu(popupMain);
}
Wt::WPushButton *upBtn = new Wt::WPushButton("UP");
upBtn->setStyleClass("btn-sm");
playlistControls->addWidget(upBtn);
Wt::WPushButton *downBtn = new Wt::WPushButton("DO");
downBtn->setStyleClass("btn-sm");
playlistControls->addWidget(downBtn);
Wt::WPushButton *delBtn = new Wt::WPushButton("DEL");
delBtn->setStyleClass("btn-sm btn-warning");
playlistControls->addWidget(delBtn);
Wt::WPushButton *clearBtn = new Wt::WPushButton("CLR");
clearBtn->setStyleClass("btn-sm btn-danger");
playlistControls->addWidget(clearBtn);
delBtn->clicked().connect(_playQueue, &PlayQueue::delSelected);
upBtn->clicked().connect(_playQueue, &PlayQueue::moveSelectedUp);
downBtn->clicked().connect(_playQueue, &PlayQueue::moveSelectedDown);
clearBtn->clicked().connect(_playQueue, &PlayQueue::delAll);
playQueueLayout->addLayout(playlistControls);
mainLayout->addWidget(playQueueContainer, 0, 0, 2, 1);
// Load the last known queue
playlistLoadToPlayqueue(CurrentQueuePlaylistName);
// Select the last known playing track
_playQueue->select(CurrentUser()->getCurPlayingTrackPos());
}
mainLayout->setRowStretch(1, 1);
mainLayout->setRowResizable(0, true, Wt::WLength(250, Wt::WLength::Pixel));
mainLayout->setColumnResizable(0, true, Wt::WLength(400, Wt::WLength::Pixel));
// Double click on track
// Set the selected tracks to the play queue
_trackView->trackDoubleClicked().connect(boost::bind(&Audio::playSelectedTracks, this, PlayQueueAddSelectedTracks));
// Double click on artist
// Set the selected tracks to the play queue
filterArtist->sigDoubleClicked().connect(boost::bind(&Audio::playSelectedTracks, this, PlayQueueAddAllTracks));
filterRelease->sigDoubleClicked().connect(boost::bind(&Audio::playSelectedTracks, this, PlayQueueAddAllTracks));
filterGenre->sigDoubleClicked().connect(boost::bind(&Audio::playSelectedTracks, this, PlayQueueAddAllTracks));
// Play button
// Set the selected tracks to the play queue
playBtn->clicked().connect(boost::bind(&Audio::playSelectedTracks, this, PlayQueueAddSelectedTracks));
// Add Button
// Add the selected tracks at the end of the play queue
addBtn->clicked().connect(this, &Audio::addSelectedTracks);
_playQueue->playTrack().connect(this, &Audio::playTrack);
_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));
_playQueue->tracksUpdated().connect(std::bind([=] () {
LMS_LOG(MOD_UI, SEV_INFO) << "Playqueue updated!";
playlistSaveFromPlayqueue(CurrentQueuePlaylistName);
}));
playlistRefreshMenus();
}
void
Audio::playlistShowSaveNewDialog()
{
Wt::WDialog *dialog = new Wt::WDialog("New playlist");
Wt::WLabel *label = new Wt::WLabel("Name", dialog->contents());
Wt::WLineEdit *edit = new Wt::WLineEdit(dialog->contents());
label->setBuddy(edit);
Wt::WLengthValidator* validator = new Wt::WLengthValidator();
validator->setMinimumLength(3);
validator->setMandatory(true);
edit->setValidator(validator);
Wt::WPushButton *save = new Wt::WPushButton("Save", dialog->footer());
save->setStyleClass("btn-success");
save->setDefault(true);
save->disable();
Wt::WPushButton *cancel = new Wt::WPushButton("Cancel", dialog->footer());
dialog->rejectWhenEscapePressed();
edit->keyWentUp().connect(std::bind([=] () {
save->setDisabled(edit->validate() != Wt::WValidator::Valid);
}));
save->clicked().connect(std::bind([=] ()
{
if (edit->validate())
dialog->accept();
}));
cancel->clicked().connect(dialog, &Wt::WDialog::reject);
dialog->finished().connect(std::bind([=] ()
{
if (dialog->result() == Wt::WDialog::Accepted)
{
playlistShowSaveDialog(edit->text().toUTF8());
}
delete dialog;
}));
dialog->show();
}
void
Audio::playlistShowSaveDialog(std::string playlistName)
{
Wt::Dbo::Transaction transaction(DboSession());
// Actually create the dialog only if the given list already exists
if (Database::Playlist::get(DboSession(), playlistName, CurrentUser()))
{
Wt::WMessageBox *messageBox = new Wt::WMessageBox
("Overwrite playlist",
Wt::WString( "Overwrite playlist '{1}'?").arg(playlistName),
Wt::Question, Wt::Yes | Wt::No);
messageBox->setModal(true);
messageBox->buttonClicked().connect(std::bind([=] () {
if (messageBox->buttonResult() == Wt::Yes)
playlistSaveFromPlayqueue(playlistName);
delete messageBox;
}));
messageBox->show();
}
else
{
playlistSaveFromPlayqueue(playlistName);
playlistRefreshMenus();
}
}
void
Audio::playlistSaveFromPlayqueue(std::string playlistName)
{
LMS_LOG(MOD_UI, SEV_INFO) << "Saving playqueue to playlist '" << playlistName << "'";
Wt::Dbo::Transaction transaction(DboSession());
Database::Playlist::pointer playlist = Database::Playlist::get(DboSession(), playlistName, CurrentUser());
if (playlist)
{
LMS_LOG(MOD_UI, SEV_INFO) << "Erasing playlist '" << playlistName << "'";
playlist.remove();
}
playlist = Database::Playlist::create(DboSession(), playlistName, false, CurrentUser());
std::vector<Database::Track::id_type> trackIds;
_playQueue->getTracks(trackIds);
int pos = 0;
BOOST_FOREACH(Database::Track::id_type trackId, trackIds)
{
Database::Track::pointer track = Database::Track::getById(DboSession(), trackId);
if (track)
Database::PlaylistEntry::create(DboSession(), track, playlist, pos++);
}
LMS_LOG(MOD_UI, SEV_INFO) << "Saving playqueue to playlist '" << playlistName << "' done. Contains " << pos << " entries";
}
void
Audio::playlistLoadToPlayqueue(std::string playlistName)
{
LMS_LOG(MOD_UI, SEV_DEBUG) << "Loading playlist '" << playlistName << "' to playqueue";
std::vector<Database::Track::id_type> entries;
{
Wt::Dbo::Transaction transaction(DboSession());
Database::Playlist::pointer playlist = Database::Playlist::get(DboSession(), playlistName, CurrentUser());
if (!playlist)
return;
entries = Database::PlaylistEntry::getEntries(DboSession(), playlist);
}
_playQueue->clear();
_playQueue->addTracks(entries);
LMS_LOG(MOD_UI, SEV_DEBUG) << "Loading playlist '" << playlistName << "' to playqueue done. " << entries.size() << " entries";
}
void
Audio::playlistShowDeleteDialog(std::string name)
{
Wt::WMessageBox *messageBox = new Wt::WMessageBox
("Delete playlist",
Wt::WString( "Deleting playlist '{1}'?").arg(name),
Wt::Question, Wt::Yes | Wt::No);
messageBox->setModal(true);
messageBox->buttonClicked().connect(std::bind([=] () {
if (messageBox->buttonResult() == Wt::Yes)
{
Wt::Dbo::Transaction transaction(DboSession());
Database::Playlist::pointer playlist = Database::Playlist::get(DboSession(), name, CurrentUser());
if (playlist)
playlist.remove();
playlistRefreshMenus();
}
delete messageBox;
}));
messageBox->show();
}
void
Audio::playlistRefreshMenus()
{
Wt::Dbo::Transaction transaction(DboSession());
// Clear playlists in each menu
LMS_LOG(MOD_UI, SEV_DEBUG) << "Save item count: " << _popupMenuSave->count();
WPopupMenuClear(_popupMenuDelete);
WPopupMenuClear(_popupMenuLoad);
WPopupMenuClear(_popupMenuSave);
_popupMenuSave->addItem("New")->triggered().connect(std::bind([=] ()
{
playlistShowSaveNewDialog();
}));
_popupMenuSave->addSeparator();
std::vector<Database::Playlist::pointer> playlists = Database::Playlist::get(DboSession(), CurrentUser());
BOOST_FOREACH(Database::Playlist::pointer playlist, playlists)
{
if (playlist->getName() == CurrentQueuePlaylistName)
continue;
// Add playlists in each menu
_popupMenuDelete->addItem(playlist->getName())->triggered().connect(std::bind([=] ()
{
playlistShowDeleteDialog(playlist->getName());
}));
_popupMenuLoad->addItem(playlist->getName())->triggered().connect(std::bind([=] ()
{
playlistLoadToPlayqueue(playlist->getName());
playlistRefreshMenus(); // in case deleted in other session
}));
_popupMenuSave->addItem(playlist->getName())->triggered().connect(std::bind([=] ()
{
playlistShowSaveDialog(playlist->getName());
}));
}
}
void
Audio::search(std::string searchText)
{
_filterChain.searchKeyword(searchText);
}
void
Audio::addSelectedTracks(void)
{
std::vector<Database::Track::id_type> trackIds;
_trackView->getSelectedTracks(trackIds);
// If nothing is selected, get the whole track list
if (trackIds.empty())
_trackView->getTracks(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
Audio::playTrack(boost::filesystem::path p, int pos)
{
LMS_LOG(MOD_UI, SEV_DEBUG) << "play track '" << p << "'";
try {
std::size_t bitrate = 0;
// Get user preferences
{
Wt::Dbo::Transaction transaction(DboSession());
bitrate = CurrentUser()->getAudioBitrate();
CurrentUser().modify()->setCurPlayingTrackPos(pos);
}
Transcode::InputMediaFile inputFile(p);
// Determine the output format using the encoding of the player
Transcode::Format::Encoding encoding;
switch (_mediaPlayer->getEncoding())
{
case Wt::WMediaPlayer::MP3: encoding = Transcode::Format::MP3; break;
case Wt::WMediaPlayer::FLA: encoding = Transcode::Format::FLA; break;
case Wt::WMediaPlayer::OGA: encoding = Transcode::Format::OGA; break;
case Wt::WMediaPlayer::WEBMA: encoding = Transcode::Format::WEBMA; break;
default:
encoding = Transcode::Format::MP3;
}
Transcode::Parameters parameters(inputFile, Transcode::Format::get(encoding));
parameters.setBitrate(Transcode::Stream::Audio, bitrate);
_mediaPlayer->load( parameters );
}
catch( std::exception &e)
{
LMS_LOG(MOD_UI, SEV_ERROR) << "Caught exception while loading '" << p << "': " << e.what();
}
}
} // namespace Desktop
} // namespace UserInterface