/* * 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 . */ #include #include #include #include #include #include #include #include #include #include #include #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 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 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 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 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 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