diff --git a/TODO b/TODO index 63681db4..5c205ccb 100644 --- a/TODO +++ b/TODO @@ -27,7 +27,6 @@ [UI] - handle internationalization [Settings] - - increase the menu's width / add margins - logout users that are being changed (loss of admin admin rights), or make sure they are still admin when they make changes - "signal not exposed" problem if a user logout and login again. bad resource destruction? [user/transcoding] @@ -43,8 +42,7 @@ - MediaPlayer: move slider using js (http://redmine.webtoolkit.eu/boards/2/topics/7924?r=8478) - TrackView : handle duration > 1 hour - TrackView : Reselect the current selected item when displaying the updated search results - - Add Year column in the release filter. Add covers too? - - Save/Load playlists + - Add covers in the release filter? [Video] - implement a decent mediaplayer diff --git a/src/Makefile.am b/src/Makefile.am index ada8b7ef..e09514d6 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -13,6 +13,7 @@ lms_SOURCES = \ $(srcdir)/cover/CoverArtGrabber.cpp \ $(srcdir)/database/DatabaseHandler.cpp \ $(srcdir)/database/MediaDirectory.cpp \ + $(srcdir)/database/Playlist.cpp \ $(srcdir)/database/SqlQuery.cpp \ $(srcdir)/database/Track.cpp \ $(srcdir)/database/User.cpp \ diff --git a/src/av/InputFormatContext.cpp b/src/av/InputFormatContext.cpp index 2aab3f67..e9432923 100644 --- a/src/av/InputFormatContext.cpp +++ b/src/av/InputFormatContext.cpp @@ -154,8 +154,6 @@ InputFormatContext::getPictures(std::vector& pictures) const else picture.mimeType = "application/octet-stream"; - LMS_LOG(MOD_AV, SEV_DEBUG) << "MIME set to '" << picture.mimeType << "'" << std::endl; - AVPacket pkt = native()->streams[i]->attached_pic; std::copy(pkt.data, pkt.data + pkt.size, std::back_inserter(picture.data)); diff --git a/src/cover/CoverArtGrabber.hpp b/src/cover/CoverArtGrabber.hpp index 757356e7..e6c809a6 100644 --- a/src/cover/CoverArtGrabber.hpp +++ b/src/cover/CoverArtGrabber.hpp @@ -23,7 +23,7 @@ #include #include "av/InputFormatContext.hpp" -#include "database/AudioTypes.hpp" +#include "database/Types.hpp" #include "CoverArt.hpp" diff --git a/src/database-updater/DatabaseUpdater.cpp b/src/database-updater/DatabaseUpdater.cpp index 5543ed32..7984f29e 100644 --- a/src/database-updater/DatabaseUpdater.cpp +++ b/src/database-updater/DatabaseUpdater.cpp @@ -25,9 +25,7 @@ #include "logger/Logger.hpp" -#include "database/MediaDirectory.hpp" -#include "database/AudioTypes.hpp" -#include "database/VideoTypes.hpp" +#include "database/Types.hpp" #include "Checksum.hpp" #include "DatabaseUpdater.hpp" diff --git a/src/database-updater/DatabaseUpdater.hpp b/src/database-updater/DatabaseUpdater.hpp index 3a3c2c3b..34d58d04 100644 --- a/src/database-updater/DatabaseUpdater.hpp +++ b/src/database-updater/DatabaseUpdater.hpp @@ -25,8 +25,6 @@ #include "metadata/MetaData.hpp" -#include "database/DatabaseHandler.hpp" -#include "database/MediaDirectory.hpp" #include "database/DatabaseHandler.hpp" namespace DatabaseUpdater { diff --git a/src/database/DatabaseHandler.cpp b/src/database/DatabaseHandler.cpp index 23dd7431..c7b3eddf 100644 --- a/src/database/DatabaseHandler.cpp +++ b/src/database/DatabaseHandler.cpp @@ -27,12 +27,6 @@ #include "logger/Logger.hpp" -// Db types -#include "AudioTypes.hpp" -#include "VideoTypes.hpp" -#include "MediaDirectory.hpp" -#include "User.hpp" - #include "DatabaseHandler.hpp" namespace Database { @@ -85,6 +79,8 @@ _dbBackend( db.string() ) _session.setConnection(_dbBackend); _session.mapClass("genre"); _session.mapClass("track"); + _session.mapClass("playlist"); + _session.mapClass("playlist_entry"); _session.mapClass("video"); _session.mapClass("media_directory"); _session.mapClass("media_directory_settings"); diff --git a/src/database/DatabaseHandler.hpp b/src/database/DatabaseHandler.hpp index bdb3c50d..4c81a10b 100644 --- a/src/database/DatabaseHandler.hpp +++ b/src/database/DatabaseHandler.hpp @@ -28,11 +28,11 @@ #include #include -#include "User.hpp" +#include "Types.hpp" namespace Database { - typedef Wt::Auth::Dbo::UserDatabase UserDatabase; +typedef Wt::Auth::Dbo::UserDatabase UserDatabase; // Session living class handling the database and the login class Handler @@ -44,8 +44,8 @@ class Handler Wt::Dbo::Session& getSession() { return _session; } - User::pointer getCurrentUser(); // get the current user, may return empty - User::pointer getUser(const Wt::Auth::User& authUser); // Get or create the given user + Wt::Dbo::ptr getCurrentUser(); // get the current user, may return empty + Wt::Dbo::ptr getUser(const Wt::Auth::User& authUser); // Get or create the given user Wt::Auth::AbstractUserDatabase& getUserDatabase(); Wt::Auth::Login& getLogin() { return _login; } diff --git a/src/database/MediaDirectory.cpp b/src/database/MediaDirectory.cpp index 53c47496..ca4209a1 100644 --- a/src/database/MediaDirectory.cpp +++ b/src/database/MediaDirectory.cpp @@ -19,7 +19,7 @@ #include -#include "MediaDirectory.hpp" +#include "Types.hpp" namespace Database { diff --git a/src/database/Playlist.cpp b/src/database/Playlist.cpp index ba312b0c..38aec290 100644 --- a/src/database/Playlist.cpp +++ b/src/database/Playlist.cpp @@ -17,22 +17,76 @@ * along with LMS. If not, see . */ - -#include "AudioTypes.hpp" +#include "Types.hpp" namespace Database { Playlist::Playlist() -: _public(false) +: _isPublic(false) { } -Playlist::Playlist(std::string name, Wt::Dbo::ptr user, bool isPublic) -: _public(isPublic), +Playlist::Playlist(std::string name, bool isPublic, Wt::Dbo::ptr user) +: _name(name), + _isPublic(isPublic), _user(user) { } +Playlist::pointer +Playlist::create(Wt::Dbo::Session& session, std::string name, bool isPublic, Wt::Dbo::ptr user) +{ + return session.add( new Playlist(name, isPublic, user) ); +} + +PlaylistEntry::PlaylistEntry() +: _pos(0) +{ + +} + +Playlist::pointer +Playlist::get(Wt::Dbo::Session& session, std::string name, Wt::Dbo::ptr user) +{ + return session.find().where("name = ? AND user_id = ?").bind(name).bind(user.id()); +} + +std::vector +Playlist::get(Wt::Dbo::Session& session, Wt::Dbo::ptr user) +{ + Wt::Dbo::collection res = session.find().where("user_id = ?").bind(user.id()).orderBy("name"); + + return std::vector(res.begin(), res.end()); +} + +PlaylistEntry::PlaylistEntry(Wt::Dbo::ptr track, Wt::Dbo::ptr playlist, int pos) +: _pos(pos), + _track(track), + _playlist(playlist) +{ + +} + +PlaylistEntry::pointer +PlaylistEntry::create(Wt::Dbo::Session& session, Wt::Dbo::ptr track, Wt::Dbo::ptr playlist, int pos) +{ + return session.add( new PlaylistEntry( track, playlist, pos) ); +} + +std::vector +PlaylistEntry::getEntries(Wt::Dbo::Session& session, Playlist::pointer playlist) +{ + typedef Wt::Dbo::collection Entries; + + Entries entries = session.find().where("playlist_id = ?").bind(playlist.id()).orderBy("pos"); + + std::vector res; + for (Entries::iterator it = entries.begin(); it != entries.end(); ++it) + res.push_back((*it)->getTrack().id()); + + return res; +} + } // namespace Database diff --git a/src/database/Playlist.hpp b/src/database/Playlist.hpp new file mode 100644 index 00000000..ba7e43e2 --- /dev/null +++ b/src/database/Playlist.hpp @@ -0,0 +1,106 @@ +/* + * 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 DATABASE_PLAYLIST_HPP +#define DATABASE_PLAYLIST_HPP + +#include + +namespace Database { + +class PlaylistEntry; +class User; +class Track; + +class Playlist +{ + public: + typedef Wt::Dbo::ptr pointer; + + Playlist(); + Playlist(std::string name, bool isPublic, Wt::Dbo::ptr user); + + // Search utility + static pointer get(Wt::Dbo::Session& session, std::string name, Wt::Dbo::ptr user); + + // ordered by name + static std::vector get(Wt::Dbo::Session& session, Wt::Dbo::ptr user); + + // Create utility + static pointer create(Wt::Dbo::Session& session, std::string name, bool isPublic, Wt::Dbo::ptr user); + + // Accessors + std::string getName() const { return _name; } + + template + void persist(Action& a) + { + Wt::Dbo::field(a, _name, "name"); + Wt::Dbo::field(a, _isPublic, "public"); + Wt::Dbo::belongsTo(a, _user, "user", Wt::Dbo::OnDeleteCascade); + Wt::Dbo::hasMany(a, _entries, Wt::Dbo::ManyToOne, "playlist"); + } + + private: + + std::string _name; + bool _isPublic; + Wt::Dbo::ptr _user; + Wt::Dbo::collection< Wt::Dbo::ptr > _entries; + +}; + +class PlaylistEntry +{ + public: + + typedef Wt::Dbo::ptr pointer; + + PlaylistEntry(); + PlaylistEntry(Wt::Dbo::ptr rack, Wt::Dbo::ptr playlist, int position); + + // Search utility + + // Get the position ordered track id list + static std::vector getEntries(Wt::Dbo::Session& session,Wt::Dbo::ptr playlist); + + // Create utility + static pointer create(Wt::Dbo::Session& session, Wt::Dbo::ptr track, Wt::Dbo::ptr playlist, int position); + + // Accesors + Wt::Dbo::ptr getTrack() const { return _track; } + + template + void persist(Action& a) + { + Wt::Dbo::field(a, _pos, "pos"); + Wt::Dbo::belongsTo(a, _track, "track", Wt::Dbo::OnDeleteCascade); + Wt::Dbo::belongsTo(a, _playlist, "playlist", Wt::Dbo::OnDeleteCascade); + } + + private: + + int _pos; + Wt::Dbo::ptr _track; + Wt::Dbo::ptr _playlist; +}; + +} // namespace Database + +#endif diff --git a/src/database/Track.cpp b/src/database/Track.cpp index dc5b77aa..3701cb89 100644 --- a/src/database/Track.cpp +++ b/src/database/Track.cpp @@ -25,7 +25,7 @@ #include "SqlQuery.hpp" -#include "AudioTypes.hpp" +#include "Types.hpp" namespace Database { diff --git a/src/database/AudioTypes.hpp b/src/database/Track.hpp similarity index 96% rename from src/database/AudioTypes.hpp rename to src/database/Track.hpp index e0124788..12274838 100644 --- a/src/database/AudioTypes.hpp +++ b/src/database/Track.hpp @@ -52,6 +52,7 @@ struct SearchFilter }; class Track; +class PlaylistEntry; class Genre { @@ -174,6 +175,7 @@ class Track Wt::Dbo::field(a, _fileChecksum, "checksum"); Wt::Dbo::field(a, _hasCover, "has_cover"); Wt::Dbo::hasMany(a, _genres, Wt::Dbo::ManyToMany, "track_genre", "", Wt::Dbo::OnDeleteCascade); + Wt::Dbo::hasMany(a, _playlistEntries, Wt::Dbo::ManyToOne, "track"); } private: @@ -198,10 +200,15 @@ class Track boost::posix_time::ptime _fileLastWrite; bool _hasCover; - Wt::Dbo::collection< Genre::pointer > _genres; // Tracks that belong to this genre + Wt::Dbo::collection< Genre::pointer > _genres; // Genres that are related to this track + Wt::Dbo::collection< Wt::Dbo::ptr > _playlistEntries; + }; + + + } // namespace database #endif diff --git a/src/database/PlaylistEntry.cpp b/src/database/Types.hpp similarity index 68% rename from src/database/PlaylistEntry.cpp rename to src/database/Types.hpp index b0b151a1..82385590 100644 --- a/src/database/PlaylistEntry.cpp +++ b/src/database/Types.hpp @@ -1,5 +1,5 @@ /* - * Copyright (C) 2014 Emeric Poupon + * Copyright (C) 2015 Emeric Poupon * * This file is part of LMS. * @@ -17,23 +17,11 @@ * along with LMS. If not, see . */ +// header file aimed to ease database class declarations -#include "AudioTypes.hpp" +#include "Track.hpp" +#include "Playlist.hpp" +#include "Video.hpp" +#include "MediaDirectory.hpp" +#include "User.hpp" -namespace Database { - -PlaylistEntry::PlaylistEntry() -: _position(0) -{ - -} - -PlaylistEntry::PlaylistEntry(Wt::Dbo::ptr track, Wt::Dbo::ptr playlist) -: _position(0), - _track(track), - _playlist(playlist) -{ - -} - -} // namespace Database diff --git a/src/database/User.cpp b/src/database/User.cpp index b8c5fa92..66f955d3 100644 --- a/src/database/User.cpp +++ b/src/database/User.cpp @@ -17,13 +17,10 @@ * along with LMS. If not, see . */ - -#include "User.hpp" - +#include "Types.hpp" namespace Database { - // must be ordered const std::vector User::audioBitrates = @@ -140,7 +137,6 @@ User::getMaxVideoBitrate(void) const return videoBitrates.back(); } - } // namespace Database diff --git a/src/database/User.hpp b/src/database/User.hpp index 23453655..3d5bbac9 100644 --- a/src/database/User.hpp +++ b/src/database/User.hpp @@ -20,7 +20,9 @@ #ifndef DATABASE_USER_HPP #define DATABASE_USER_HPP +#include +#include #include namespace Database { @@ -28,6 +30,8 @@ namespace Database { class User; typedef Wt::Auth::Dbo::AuthInfo AuthInfo; +class Playlist; + class User { public: @@ -68,6 +72,7 @@ class User { Wt::Dbo::field(a, _isAdmin, "admin"); Wt::Dbo::field(a, _audioBitrate, "audio_bitrate"); Wt::Dbo::field(a, _videoBitrate, "video_bitrate"); + Wt::Dbo::hasMany(a, _playlists, Wt::Dbo::ManyToOne, "user"); } private: @@ -87,6 +92,8 @@ class User { int _audioBitrate; int _videoBitrate; + Wt::Dbo::collection< Wt::Dbo::ptr > _playlists; + }; } // namespace Databas' diff --git a/src/database/Video.cpp b/src/database/Video.cpp index e724cfcb..5d2061cb 100644 --- a/src/database/Video.cpp +++ b/src/database/Video.cpp @@ -17,7 +17,7 @@ * along with LMS. If not, see . */ -#include "VideoTypes.hpp" +#include "Types.hpp" namespace Database { diff --git a/src/database/VideoTypes.hpp b/src/database/Video.hpp similarity index 100% rename from src/database/VideoTypes.hpp rename to src/database/Video.hpp diff --git a/src/remote/server/AudioCollectionRequestHandler.cpp b/src/remote/server/AudioCollectionRequestHandler.cpp index 98d9e37f..b4b09932 100644 --- a/src/remote/server/AudioCollectionRequestHandler.cpp +++ b/src/remote/server/AudioCollectionRequestHandler.cpp @@ -27,8 +27,7 @@ #include "AudioCollectionRequestHandler.hpp" -#include "database/AudioTypes.hpp" -#include "database/MediaDirectory.hpp" +#include "database/Types.hpp" #include "cover/CoverArtGrabber.hpp" namespace Remote { diff --git a/src/remote/server/MediaRequestHandler.cpp b/src/remote/server/MediaRequestHandler.cpp index 40a0c4e2..7b9734be 100644 --- a/src/remote/server/MediaRequestHandler.cpp +++ b/src/remote/server/MediaRequestHandler.cpp @@ -21,7 +21,7 @@ #include "MediaRequestHandler.hpp" -#include "database/AudioTypes.hpp" +#include "database/Types.hpp" namespace Remote { namespace Server { diff --git a/src/transcode/Format.cpp b/src/transcode/Format.cpp index ce4a0dac..8e35ff20 100644 --- a/src/transcode/Format.cpp +++ b/src/transcode/Format.cpp @@ -30,7 +30,7 @@ const std::vector Format::_supportedFormats { {Format::OGA, Format::Audio, "audio/ogg", "Ogg"}, {Format::OGV, Format::Video, "video/ogg", "Ogg"}, - {Format::MP3, Format::Audio, "audio/mp3", "MP3"}, + {Format::MP3, Format::Audio, "audio/mpeg", "MP3"}, {Format::WEBMA, Format::Audio, "audio/webm", "WebM"}, {Format::WEBMV, Format::Video, "video/webm", "WebM"}, {Format::FLV, Format::Video, "video/x-flv", "Flash Video"}, diff --git a/src/transcode/Parameters.cpp b/src/transcode/Parameters.cpp index 92e61d92..e129dbac 100644 --- a/src/transcode/Parameters.cpp +++ b/src/transcode/Parameters.cpp @@ -27,13 +27,6 @@ namespace Transcode { -std::string -getMimeType(Format format) -{ - //TODO - return ""; -} - Parameters::Parameters(const InputMediaFile& inputMediaFile, const Format& outputFormat) : diff --git a/src/ui/audio/Audio.cpp b/src/ui/audio/Audio.cpp index d3e337ef..95be6280 100644 --- a/src/ui/audio/Audio.cpp +++ b/src/ui/audio/Audio.cpp @@ -24,7 +24,12 @@ #include #include #include -#include +#include + +#include +#include +#include +#include #include "logger/Logger.hpp" @@ -33,6 +38,16 @@ #include "Audio.hpp" +namespace { + +void WPopupMenuClear(Wt::WPopupMenu* menu) +{ + while(menu->count() > 0) + menu->removeItem(menu->itemAt(0)); +} + +} + namespace UserInterface { Audio::Audio(SessionData& sessionData, Wt::WContainerWidget* parent) @@ -94,7 +109,6 @@ _playQueue(nullptr) // Playlist/PlayQueue { Wt::Dbo::Transaction transaction(_db.getSession()); - Database::User::pointer user = _db.getCurrentUser(); Wt::WContainerWidget* playQueueContainer = new Wt::WContainerWidget(); playQueueContainer->setStyleClass("playqueue"); @@ -108,13 +122,25 @@ _playQueue(nullptr) Wt::WHBoxLayout* playlistControls = new Wt::WHBoxLayout(); - Wt::WPushButton *saveBtn = new Wt::WPushButton("Save"); - saveBtn->setStyleClass("btn-sm"); + Wt::WPushButton *playlistBtn = new Wt::WPushButton("Playlist"); + playlistBtn->setStyleClass("btn-sm btn-primary"); + playlistControls->addWidget(playlistBtn); - playlistControls->addWidget(saveBtn); - Wt::WPushButton *loadBtn = new Wt::WPushButton("Load"); - loadBtn->setStyleClass("btn-sm"); - playlistControls->addWidget(loadBtn); + // 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"); @@ -134,27 +160,6 @@ _playQueue(nullptr) downBtn->clicked().connect(_playQueue, &PlayQueue::moveSelectedDown); clearBtn->clicked().connect(_playQueue, &PlayQueue::delAll); - // Load menu - { - Wt::WPopupMenu *popup = new Wt::WPopupMenu(); - - popup->addItem("Metal"); - popup->addItem("Test"); - - loadBtn->setMenu(popup); - } - - // Save Menu - { - Wt::WPopupMenu *popup = new Wt::WPopupMenu(); - - popup->addItem("New"); - popup->addSeparator(); - popup->addItem("Metal"); - popup->addItem("Test"); - - saveBtn->setMenu(popup); - } playQueueLayout->addLayout(playlistControls); @@ -190,6 +195,226 @@ _playQueue(nullptr) _mediaPlayer->playPrevious().connect(_playQueue, &PlayQueue::playPrevious); _mediaPlayer->shuffle().connect(boost::bind(&PlayQueue::setShuffle, _playQueue, _1)); _mediaPlayer->loop().connect(boost::bind(&PlayQueue::setLoop,_playQueue, _1)); + + 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(_db.getSession()); + + Database::User::pointer user = _db.getCurrentUser(); + if (!user) + return; + + // Actually create the dialog only if the given list already exists + if (Database::Playlist::get(_db.getSession(), playlistName, user)) + { + 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(_db.getSession()); + + Database::User::pointer user = _db.getCurrentUser(); + if (!user) + return; + + Database::Playlist::pointer playlist = Database::Playlist::get(_db.getSession(), playlistName, user); + if (playlist) + { + LMS_LOG(MOD_UI, SEV_INFO) << "Erasing playlist '" << playlistName << "'"; + playlist.remove(); + } + + playlist = Database::Playlist::create(_db.getSession(), playlistName, false, user); + + std::vector trackIds; + _playQueue->getTracks(trackIds); + + int pos = 0; + BOOST_FOREACH(Database::Track::id_type trackId, trackIds) + { + Database::Track::pointer track = Database::Track::getById(_db.getSession(), trackId); + + if (track) + Database::PlaylistEntry::create(_db.getSession(), 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"; + + Wt::Dbo::Transaction transaction(_db.getSession()); + + Database::User::pointer user = _db.getCurrentUser(); + if (!user) + return; + + Database::Playlist::pointer playlist = Database::Playlist::get(_db.getSession(), playlistName, user); + if (!playlist) + return; + + std::vector entries = Database::PlaylistEntry::getEntries(_db.getSession(), 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(_db.getSession()); + Database::User::pointer user = _db.getCurrentUser(); + if (!user) + return; + + Database::Playlist::pointer playlist = Database::Playlist::get(_db.getSession(), name, user); + if (playlist) + playlist.remove(); + + playlistRefreshMenus(); + } + + delete messageBox; + })); + + messageBox->show(); +} + +void +Audio::playlistRefreshMenus() +{ + Wt::Dbo::Transaction transaction(_db.getSession()); + + Database::User::pointer user = _db.getCurrentUser(); + if (!user) + return; + + // 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(_db.getSession(), user); + + BOOST_FOREACH(Database::Playlist::pointer playlist, playlists) + { + // 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 @@ -277,7 +502,18 @@ Audio::playTrack(boost::filesystem::path p) Transcode::InputMediaFile inputFile(p); - Transcode::Parameters parameters(inputFile, Transcode::Format::get(Transcode::Format::OGA)); + // Determine the output format using the encoding of the player + Transcode::Format::Encoding encoding; + switch(AudioMediaPlayer::getEncoding()) + { + case Wt::WMediaPlayer::MP3: encoding = Transcode::Format::MP3; break; + case Wt::WMediaPlayer::M4A: encoding = Transcode::Format::M4A; break; + case Wt::WMediaPlayer::OGA: encoding = Transcode::Format::OGA; break; + default: + encoding = Transcode::Format::MP3; + } + + Transcode::Parameters parameters(inputFile, Transcode::Format::get(encoding)); parameters.setBitrate(Transcode::Stream::Audio, bitrate); diff --git a/src/ui/audio/Audio.hpp b/src/ui/audio/Audio.hpp index 465bd8fa..c09fff4b 100644 --- a/src/ui/audio/Audio.hpp +++ b/src/ui/audio/Audio.hpp @@ -22,6 +22,7 @@ #include +#include #include #include "common/SessionData.hpp" @@ -45,6 +46,13 @@ class Audio : public Wt::WContainerWidget private: + void playlistSaveFromPlayqueue(std::string name); + void playlistLoadToPlayqueue(std::string name); + void playlistShowSaveNewDialog(); + void playlistShowSaveDialog(std::string name); + void playlistShowDeleteDialog(std::string name); + void playlistRefreshMenus(); + void playTrack(boost::filesystem::path p); enum PlayQueueAddType @@ -66,6 +74,9 @@ class Audio : public Wt::WContainerWidget FilterChain _filterChain; + Wt::WPopupMenu* _popupMenuSave; + Wt::WPopupMenu* _popupMenuLoad; + Wt::WPopupMenu* _popupMenuDelete; }; } // namespace UserInterface diff --git a/src/ui/audio/AudioMediaPlayer.cpp b/src/ui/audio/AudioMediaPlayer.cpp index 2a38a8e5..55f1bcc6 100644 --- a/src/ui/audio/AudioMediaPlayer.cpp +++ b/src/ui/audio/AudioMediaPlayer.cpp @@ -23,11 +23,24 @@ #include #include #include +#include +#include #include "AudioMediaPlayer.hpp" namespace UserInterface { +Wt::WMediaPlayer::Encoding +AudioMediaPlayer::getEncoding() +{ + const Wt::WEnvironment& env = Wt::WApplication::instance()->environment(); + + if (env.agentIsIE()) + return Wt::WMediaPlayer::MP3; + else + return Wt::WMediaPlayer::OGA; +} + AudioMediaPlayer::AudioMediaPlayer( Wt::WContainerWidget *parent) : Wt::WContainerWidget(parent), _mediaResource(nullptr) @@ -98,7 +111,7 @@ AudioMediaPlayer::AudioMediaPlayer( Wt::WContainerWidget *parent) controlsLayout->addWidget(shuffle); _mediaPlayer = new Wt::WMediaPlayer( Wt::WMediaPlayer::Audio, btnContainer ); - _mediaPlayer->addSource( Wt::WMediaPlayer::OGA, "" ); + _mediaPlayer->addSource( getEncoding(), "" ); _mediaPlayer->ended().connect(this, &AudioMediaPlayer::handleTrackEnded); _mediaPlayer->setControlsWidget( 0 ); @@ -142,7 +155,7 @@ AudioMediaPlayer::loadPlayer(void) _mediaResource = new AvConvTranscodeStreamResource( *_currentParameters, this ); _mediaInternalLink.setResource( _mediaResource ); - _mediaPlayer->addSource( Wt::WMediaPlayer::OGA, _mediaInternalLink ); + _mediaPlayer->addSource( getEncoding(), _mediaInternalLink ); } void diff --git a/src/ui/audio/AudioMediaPlayer.hpp b/src/ui/audio/AudioMediaPlayer.hpp index 30f3a18d..fa7fa369 100644 --- a/src/ui/audio/AudioMediaPlayer.hpp +++ b/src/ui/audio/AudioMediaPlayer.hpp @@ -38,6 +38,9 @@ class AudioMediaPlayer : public Wt::WContainerWidget { public: + // Encoding is set based on environment + static Wt::WMediaPlayer::Encoding getEncoding(); + AudioMediaPlayer( Wt::WContainerWidget *parent = 0); void load(const Transcode::Parameters& parameters); diff --git a/src/ui/audio/Filter.hpp b/src/ui/audio/Filter.hpp index 17afe7d2..09c19327 100644 --- a/src/ui/audio/Filter.hpp +++ b/src/ui/audio/Filter.hpp @@ -22,7 +22,7 @@ #include -#include "database/AudioTypes.hpp" +#include "database/Types.hpp" namespace UserInterface { diff --git a/src/ui/audio/PlayQueue.cpp b/src/ui/audio/PlayQueue.cpp index 13f313e3..b854cc84 100644 --- a/src/ui/audio/PlayQueue.cpp +++ b/src/ui/audio/PlayQueue.cpp @@ -599,7 +599,16 @@ PlayQueue::renumber(int firstId, int lastId) _model->setData( i, 1, i + 1); } - +void +PlayQueue::getTracks(std::vector& trackIds) const +{ + // Now add each entry in the playlist + for (int i = 0; i < _model->rowCount(); ++i) + { + Database::Track::id_type trackId = boost::any_cast(_model->data(i, COLUMN_ID_TRACK_ID, Wt::UserRole)); + trackIds.push_back(trackId); + } +} } // namespace UserInterface diff --git a/src/ui/audio/PlayQueue.hpp b/src/ui/audio/PlayQueue.hpp index 003e8518..72ef106a 100644 --- a/src/ui/audio/PlayQueue.hpp +++ b/src/ui/audio/PlayQueue.hpp @@ -24,7 +24,6 @@ #include #include "database/DatabaseHandler.hpp" -#include "database/AudioTypes.hpp" #include "resource/CoverResource.hpp" @@ -39,6 +38,7 @@ class PlayQueue : public Wt::WTableView PlayQueue(Database::Handler& db, Wt::WContainerWidget* parent = 0); void addTracks(const std::vector& trackIds); + void getTracks(std::vector& trackIds) const; void clear(void); @@ -61,8 +61,6 @@ class PlayQueue : public Wt::WTableView Wt::Signal< boost::filesystem::path >& playTrack() { return _sigTrackPlay; } // Slots - void saveToPlaylist(std::string& playListName); - void loadFromPlaylist(std::string& playListName); void handlePlaybackComplete(void); private: diff --git a/src/ui/audio/TableFilter.cpp b/src/ui/audio/TableFilter.cpp index 00ab482e..56f0c48b 100644 --- a/src/ui/audio/TableFilter.cpp +++ b/src/ui/audio/TableFilter.cpp @@ -21,7 +21,7 @@ #include -#include "database/AudioTypes.hpp" +#include "database/Types.hpp" #include "logger/Logger.hpp" diff --git a/src/ui/audio/TrackView.hpp b/src/ui/audio/TrackView.hpp index 6b0a24bf..cc8ae23f 100644 --- a/src/ui/audio/TrackView.hpp +++ b/src/ui/audio/TrackView.hpp @@ -24,7 +24,6 @@ #include #include "database/DatabaseHandler.hpp" -#include "database/AudioTypes.hpp" #include "Filter.hpp" diff --git a/src/ui/resource/AvConvTranscodeStreamResource.cpp b/src/ui/resource/AvConvTranscodeStreamResource.cpp index 53acf85a..a8e22ea1 100644 --- a/src/ui/resource/AvConvTranscodeStreamResource.cpp +++ b/src/ui/resource/AvConvTranscodeStreamResource.cpp @@ -58,6 +58,7 @@ AvConvTranscodeStreamResource::handleRequest(const Wt::Http::Request& request, LMS_LOG(MOD_UI, SEV_DEBUG) << "Launching transcoder"; transcoder = std::make_shared( _parameters); + LMS_LOG(MOD_UI, SEV_DEBUG) << "Mime type set to '" << _parameters.getOutputFormat().getMimeType() << "'"; response.setMimeType(_parameters.getOutputFormat().getMimeType()); } diff --git a/src/ui/resource/CoverResource.hpp b/src/ui/resource/CoverResource.hpp index fdc45277..59efde51 100644 --- a/src/ui/resource/CoverResource.hpp +++ b/src/ui/resource/CoverResource.hpp @@ -26,7 +26,6 @@ #include #include "database/DatabaseHandler.hpp" -#include "database/AudioTypes.hpp" namespace UserInterface { diff --git a/src/ui/video/VideoDatabaseWidget.cpp b/src/ui/video/VideoDatabaseWidget.cpp index 45b2fa2b..b8e31456 100644 --- a/src/ui/video/VideoDatabaseWidget.cpp +++ b/src/ui/video/VideoDatabaseWidget.cpp @@ -26,8 +26,6 @@ #include #include "transcode/Parameters.hpp" -#include "database/VideoTypes.hpp" -#include "database/MediaDirectory.hpp" #include "VideoDatabaseWidget.hpp" diff --git a/src/ui/video/VideoWidget.cpp b/src/ui/video/VideoWidget.cpp index 1ad7761d..566a392f 100644 --- a/src/ui/video/VideoWidget.cpp +++ b/src/ui/video/VideoWidget.cpp @@ -68,6 +68,7 @@ VideoWidget::playVideo(boost::filesystem::path p) // Get user preferences { Wt::Dbo::Transaction transaction(_sessionData.getDatabaseHandler().getSession()); + Database::User::pointer user = _sessionData.getDatabaseHandler().getCurrentUser(); if (user) { diff --git a/test/CheckDatabaseUser.cpp b/test/CheckDatabaseUser.cpp index 25b22515..45a0f432 100644 --- a/test/CheckDatabaseUser.cpp +++ b/test/CheckDatabaseUser.cpp @@ -18,7 +18,6 @@ */ #include "database/DatabaseHandler.hpp" -#include #include int main(void) diff --git a/test/DatabaseIntegrity.cpp b/test/DatabaseIntegrity.cpp index 1bd22e0f..4c0c0265 100644 --- a/test/DatabaseIntegrity.cpp +++ b/test/DatabaseIntegrity.cpp @@ -25,8 +25,6 @@ #include "database/DatabaseHandler.hpp" -#include "database/AudioTypes.hpp" - int main(void) { diff --git a/test/Makefile.am b/test/Makefile.am index a80eb388..0ce574a1 100644 --- a/test/Makefile.am +++ b/test/Makefile.am @@ -30,7 +30,7 @@ remote_client_CXXFLAGS=-std=c++11 -Wall -Wextra -DBOOST_LOG_DYN_LINK -I$(top_src database_user_SOURCES = \ $(srcdir)/CheckDatabaseUser.cpp \ $(top_srcdir)/src/logger/Logger.cpp \ - $(top_srcdir)/src/database/Genre.cpp \ + $(top_srcdir)/src/database/Playlist.cpp \ $(top_srcdir)/src/database/Track.cpp \ $(top_srcdir)/src/database/DatabaseHandler.cpp \ $(top_srcdir)/src/database/MediaDirectory.cpp \ @@ -44,7 +44,7 @@ database_user_CXXFLAGS=-std=c++11 -Wall -Wextra -DBOOST_LOG_DYN_LINK -I$(top_src database_integrity_SOURCES = \ $(srcdir)/DatabaseIntegrity.cpp \ $(top_srcdir)/src/logger/Logger.cpp \ - $(top_srcdir)/src/database/Genre.cpp \ + $(top_srcdir)/src/database/Playlist.cpp \ $(top_srcdir)/src/database/Track.cpp \ $(top_srcdir)/src/database/DatabaseHandler.cpp \ $(top_srcdir)/src/database/MediaDirectory.cpp \