From e5a5e7dbc94a36111a33024e51014a793420802c Mon Sep 17 00:00:00 2001 From: emeric Date: Sat, 17 Jan 2015 17:23:33 +0100 Subject: [PATCH 1/7] [UI] Splitted code to prepare mobile audio implementation --- src/Makefile.am | 15 ++-- src/ui/LmsApplication.cpp | 27 ++++-- src/ui/audio/Audio.hpp | 54 ++---------- .../audio/{ => desktop}/AudioMediaPlayer.cpp | 2 + .../audio/{ => desktop}/AudioMediaPlayer.hpp | 2 + .../{Audio.cpp => desktop/DesktopAudio.cpp} | 9 +- src/ui/audio/desktop/DesktopAudio.hpp | 88 +++++++++++++++++++ src/ui/audio/{ => desktop}/Filter.hpp | 2 + src/ui/audio/{ => desktop}/FilterChain.cpp | 2 + src/ui/audio/{ => desktop}/FilterChain.hpp | 2 + .../{ => desktop}/KeywordSearchFilter.cpp | 2 + .../{ => desktop}/KeywordSearchFilter.hpp | 2 + src/ui/audio/{ => desktop}/PlayQueue.cpp | 2 + src/ui/audio/{ => desktop}/PlayQueue.hpp | 3 +- src/ui/audio/{ => desktop}/TableFilter.cpp | 2 + src/ui/audio/{ => desktop}/TableFilter.hpp | 2 + src/ui/audio/{ => desktop}/TrackView.cpp | 2 + src/ui/audio/{ => desktop}/TrackView.hpp | 2 + src/ui/audio/mobile/MobileAudio.cpp | 36 ++++++++ src/ui/audio/mobile/MobileAudio.hpp | 42 +++++++++ 20 files changed, 232 insertions(+), 66 deletions(-) rename src/ui/audio/{ => desktop}/AudioMediaPlayer.cpp (99%) rename src/ui/audio/{ => desktop}/AudioMediaPlayer.hpp (98%) rename src/ui/audio/{Audio.cpp => desktop/DesktopAudio.cpp} (99%) create mode 100644 src/ui/audio/desktop/DesktopAudio.hpp rename src/ui/audio/{ => desktop}/Filter.hpp (97%) rename src/ui/audio/{ => desktop}/FilterChain.cpp (97%) rename src/ui/audio/{ => desktop}/FilterChain.hpp (96%) rename src/ui/audio/{ => desktop}/KeywordSearchFilter.cpp (97%) rename src/ui/audio/{ => desktop}/KeywordSearchFilter.hpp (96%) rename src/ui/audio/{ => desktop}/PlayQueue.cpp (99%) rename src/ui/audio/{ => desktop}/PlayQueue.hpp (98%) rename src/ui/audio/{ => desktop}/TableFilter.cpp (99%) rename src/ui/audio/{ => desktop}/TableFilter.hpp (98%) rename src/ui/audio/{ => desktop}/TrackView.cpp (98%) rename src/ui/audio/{ => desktop}/TrackView.hpp (97%) create mode 100644 src/ui/audio/mobile/MobileAudio.cpp create mode 100644 src/ui/audio/mobile/MobileAudio.hpp diff --git a/src/Makefile.am b/src/Makefile.am index e09514d6..5507f6e3 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -40,13 +40,14 @@ lms_SOURCES = \ $(srcdir)/transcode/InputMediaFile.cpp \ $(srcdir)/ui/LmsApplication.cpp \ $(srcdir)/ui/auth/LmsAuth.cpp \ - $(srcdir)/ui/audio/Audio.cpp \ - $(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/audio/desktop/DesktopAudio.cpp \ + $(srcdir)/ui/audio/desktop/AudioMediaPlayer.cpp \ + $(srcdir)/ui/audio/desktop/FilterChain.cpp \ + $(srcdir)/ui/audio/desktop/KeywordSearchFilter.cpp \ + $(srcdir)/ui/audio/desktop/PlayQueue.cpp \ + $(srcdir)/ui/audio/desktop/TableFilter.cpp \ + $(srcdir)/ui/audio/desktop/TrackView.cpp \ + $(srcdir)/ui/audio/mobile/MobileAudio.cpp \ $(srcdir)/ui/common/DirectoryValidator.cpp \ $(srcdir)/ui/common/LineEdit.cpp \ $(srcdir)/ui/common/SessionData.cpp \ diff --git a/src/ui/LmsApplication.cpp b/src/ui/LmsApplication.cpp index ba7cfc42..052a1908 100644 --- a/src/ui/LmsApplication.cpp +++ b/src/ui/LmsApplication.cpp @@ -17,17 +17,15 @@ * along with LMS. If not, see . */ +#include #include #include -#include #include #include #include #include #include -#include #include -#include #include #include "logger/Logger.hpp" @@ -36,7 +34,8 @@ #include "settings/SettingsFirstConnectionFormView.hpp" #include "auth/LmsAuth.hpp" -#include "audio/Audio.hpp" +#include "audio/desktop/DesktopAudio.hpp" +#include "audio/mobile/MobileAudio.hpp" #include "video/VideoWidget.hpp" #include "common/LineEdit.hpp" @@ -46,6 +45,16 @@ namespace skeletons { extern const char *AuthStrings_xml1; } +namespace { + +bool agentIsMobile() +{ + const Wt::WEnvironment& env = Wt::WApplication::instance()->environment(); + + return (env.agentIsIEMobile() || env.agentIsMobileWebKit()); +} +} + namespace UserInterface { @@ -150,7 +159,15 @@ LmsApplication::handleAuthEvent(void) Wt::WMenu *leftMenu = new Wt::WMenu(contentsStack); navigation->addMenu(leftMenu); - Audio *audio = new Audio(_sessionData); + const Wt::WEnvironment& env = Wt::WApplication::instance()->environment(); + + Audio *audio; + + if (agentIsMobile()) + audio = new Desktop::Audio(_sessionData); + else + audio = new Mobile::Audio(); + VideoWidget *videoWidget = new VideoWidget(_sessionData); leftMenu->addItem("Audio", audio); diff --git a/src/ui/audio/Audio.hpp b/src/ui/audio/Audio.hpp index c09fff4b..422b0cca 100644 --- a/src/ui/audio/Audio.hpp +++ b/src/ui/audio/Audio.hpp @@ -17,69 +17,25 @@ * along with LMS. If not, see . */ -#ifndef AUDIO_HPP -#define AUDIO_HPP +#ifndef UI_AUDIO_HPP +#define UI_AUDIO_HPP #include - -#include #include -#include "common/SessionData.hpp" - -#include "AudioMediaPlayer.hpp" -#include "TrackView.hpp" -#include "PlayQueue.hpp" - -#include "FilterChain.hpp" - namespace UserInterface { class Audio : public Wt::WContainerWidget { - public: - Audio(SessionData& sessionData, Wt::WContainerWidget* parent = 0); + Audio(Wt::WContainerWidget *parent) : Wt::WContainerWidget(parent) {} + virtual ~Audio() {} - void search(const std::string& searchText); + virtual void search(std::string text) = 0; - 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 - { - PlayQueueAddAllTracks, - PlayQueueAddSelectedTracks, - }; - - void playSelectedTracks(PlayQueueAddType addType); - void addSelectedTracks(); - - void handlePlaylistSelected(Wt::WString name); - - Database::Handler& _db; - - AudioMediaPlayer* _mediaPlayer; - TrackView* _trackView; - PlayQueue* _playQueue; - - FilterChain _filterChain; - - Wt::WPopupMenu* _popupMenuSave; - Wt::WPopupMenu* _popupMenuLoad; - Wt::WPopupMenu* _popupMenuDelete; }; } // namespace UserInterface #endif - diff --git a/src/ui/audio/AudioMediaPlayer.cpp b/src/ui/audio/desktop/AudioMediaPlayer.cpp similarity index 99% rename from src/ui/audio/AudioMediaPlayer.cpp rename to src/ui/audio/desktop/AudioMediaPlayer.cpp index 55f1bcc6..e0ee418d 100644 --- a/src/ui/audio/AudioMediaPlayer.cpp +++ b/src/ui/audio/desktop/AudioMediaPlayer.cpp @@ -29,6 +29,7 @@ #include "AudioMediaPlayer.hpp" namespace UserInterface { +namespace Desktop { Wt::WMediaPlayer::Encoding AudioMediaPlayer::getEncoding() @@ -224,4 +225,5 @@ AudioMediaPlayer::handleVolumeSliderMoved(int value) _mediaPlayer->setVolume( value / 100. ); } +} // namespace Desktop } // namespace UserInterface diff --git a/src/ui/audio/AudioMediaPlayer.hpp b/src/ui/audio/desktop/AudioMediaPlayer.hpp similarity index 98% rename from src/ui/audio/AudioMediaPlayer.hpp rename to src/ui/audio/desktop/AudioMediaPlayer.hpp index fa7fa369..2f6a00fd 100644 --- a/src/ui/audio/AudioMediaPlayer.hpp +++ b/src/ui/audio/desktop/AudioMediaPlayer.hpp @@ -33,6 +33,7 @@ #include "resource/AvConvTranscodeStreamResource.hpp" namespace UserInterface { +namespace Desktop { class AudioMediaPlayer : public Wt::WContainerWidget { @@ -88,6 +89,7 @@ class AudioMediaPlayer : public Wt::WContainerWidget }; +} // namespace Desktop } // namespace UserInterface #endif diff --git a/src/ui/audio/Audio.cpp b/src/ui/audio/desktop/DesktopAudio.cpp similarity index 99% rename from src/ui/audio/Audio.cpp rename to src/ui/audio/desktop/DesktopAudio.cpp index 95be6280..b1e7d103 100644 --- a/src/ui/audio/Audio.cpp +++ b/src/ui/audio/desktop/DesktopAudio.cpp @@ -36,7 +36,7 @@ #include "TableFilter.hpp" #include "KeywordSearchFilter.hpp" -#include "Audio.hpp" +#include "DesktopAudio.hpp" namespace { @@ -49,9 +49,10 @@ void WPopupMenuClear(Wt::WPopupMenu* menu) } namespace UserInterface { +namespace Desktop { Audio::Audio(SessionData& sessionData, Wt::WContainerWidget* parent) -: Wt::WContainerWidget(parent), +: UserInterface::Audio(parent), _db(sessionData.getDatabaseHandler()), _mediaPlayer(nullptr), _trackView(nullptr), @@ -418,7 +419,7 @@ Audio::playlistRefreshMenus() } void -Audio::search(const std::string& searchText) +Audio::search(std::string searchText) { _filterChain.searchKeyword(searchText); } @@ -525,6 +526,6 @@ Audio::playTrack(boost::filesystem::path p) } } - +} // namespace Desktop } // namespace UserInterface diff --git a/src/ui/audio/desktop/DesktopAudio.hpp b/src/ui/audio/desktop/DesktopAudio.hpp new file mode 100644 index 00000000..19b70f5f --- /dev/null +++ b/src/ui/audio/desktop/DesktopAudio.hpp @@ -0,0 +1,88 @@ +/* + * 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 . + */ + +#ifndef UI_AUDIO_DESKTOP_HPP +#define UI_AUDIO_DESKTOP_HPP + +#include + +#include + +#include "common/SessionData.hpp" + +#include "AudioMediaPlayer.hpp" +#include "TrackView.hpp" +#include "PlayQueue.hpp" + +#include "FilterChain.hpp" + +#include "audio/Audio.hpp" + +namespace UserInterface { +namespace Desktop { + +class Audio : public UserInterface::Audio +{ + + public: + + Audio(SessionData& sessionData, Wt::WContainerWidget* parent = 0); + + void search(std::string searchText); + + 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 + { + PlayQueueAddAllTracks, + PlayQueueAddSelectedTracks, + }; + + void playSelectedTracks(PlayQueueAddType addType); + void addSelectedTracks(); + + void handlePlaylistSelected(Wt::WString name); + + Database::Handler& _db; + + AudioMediaPlayer* _mediaPlayer; + TrackView* _trackView; + PlayQueue* _playQueue; + + FilterChain _filterChain; + + Wt::WPopupMenu* _popupMenuSave; + Wt::WPopupMenu* _popupMenuLoad; + Wt::WPopupMenu* _popupMenuDelete; +}; + +} // namespace Desktop +} // namespace UserInterface + +#endif + diff --git a/src/ui/audio/Filter.hpp b/src/ui/audio/desktop/Filter.hpp similarity index 97% rename from src/ui/audio/Filter.hpp rename to src/ui/audio/desktop/Filter.hpp index 09c19327..194f2867 100644 --- a/src/ui/audio/Filter.hpp +++ b/src/ui/audio/desktop/Filter.hpp @@ -25,6 +25,7 @@ #include "database/Types.hpp" namespace UserInterface { +namespace Desktop { class Filter { @@ -58,6 +59,7 @@ class Filter Wt::Signal _update; }; +} // namespace Dekstop } // namespace UserInterface #endif diff --git a/src/ui/audio/FilterChain.cpp b/src/ui/audio/desktop/FilterChain.cpp similarity index 97% rename from src/ui/audio/FilterChain.cpp rename to src/ui/audio/desktop/FilterChain.cpp index b3b0ab96..d94ddea5 100644 --- a/src/ui/audio/FilterChain.cpp +++ b/src/ui/audio/desktop/FilterChain.cpp @@ -20,6 +20,7 @@ #include "FilterChain.hpp" namespace UserInterface { +namespace Desktop { FilterChain::FilterChain() @@ -69,5 +70,6 @@ FilterChain::updateFilters(std::size_t startIdx) _refreshingFilters = false; } +} // namespace Desktop } // namespace UserInterface diff --git a/src/ui/audio/FilterChain.hpp b/src/ui/audio/desktop/FilterChain.hpp similarity index 96% rename from src/ui/audio/FilterChain.hpp rename to src/ui/audio/desktop/FilterChain.hpp index 8ffd157a..c9625941 100644 --- a/src/ui/audio/FilterChain.hpp +++ b/src/ui/audio/desktop/FilterChain.hpp @@ -26,6 +26,7 @@ #include "KeywordSearchFilter.hpp" namespace UserInterface { +namespace Desktop { // FilterChain class FilterChain @@ -52,6 +53,7 @@ class FilterChain bool _refreshingFilters; }; +} // namespace Desktop } // namespace UserInterface #endif diff --git a/src/ui/audio/KeywordSearchFilter.cpp b/src/ui/audio/desktop/KeywordSearchFilter.cpp similarity index 97% rename from src/ui/audio/KeywordSearchFilter.cpp rename to src/ui/audio/desktop/KeywordSearchFilter.cpp index 8f896939..ab39b9ed 100644 --- a/src/ui/audio/KeywordSearchFilter.cpp +++ b/src/ui/audio/desktop/KeywordSearchFilter.cpp @@ -24,6 +24,7 @@ #include "KeywordSearchFilter.hpp" namespace UserInterface { +namespace Desktop { KeywordSearchFilter::KeywordSearchFilter() { @@ -60,5 +61,6 @@ KeywordSearchFilter::getConstraint(Database::SearchFilter& filter) } } +} // namespace Desktop } // namespace UserInterface diff --git a/src/ui/audio/KeywordSearchFilter.hpp b/src/ui/audio/desktop/KeywordSearchFilter.hpp similarity index 96% rename from src/ui/audio/KeywordSearchFilter.hpp rename to src/ui/audio/desktop/KeywordSearchFilter.hpp index 5ff2df87..acf62b0b 100644 --- a/src/ui/audio/KeywordSearchFilter.hpp +++ b/src/ui/audio/desktop/KeywordSearchFilter.hpp @@ -25,6 +25,7 @@ #include "Filter.hpp" namespace UserInterface { +namespace Desktop { class KeywordSearchFilter : public Filter { @@ -46,6 +47,7 @@ class KeywordSearchFilter : public Filter std::string _lastEmittedText; }; +} // namespace Desktop } // namespace UserInterface #endif diff --git a/src/ui/audio/PlayQueue.cpp b/src/ui/audio/desktop/PlayQueue.cpp similarity index 99% rename from src/ui/audio/PlayQueue.cpp rename to src/ui/audio/desktop/PlayQueue.cpp index b854cc84..62552bee 100644 --- a/src/ui/audio/PlayQueue.cpp +++ b/src/ui/audio/desktop/PlayQueue.cpp @@ -58,6 +58,7 @@ namespace { } namespace UserInterface { +namespace Desktop { enum ColumnId { @@ -610,5 +611,6 @@ PlayQueue::getTracks(std::vector& trackIds) const } } +} // namespace Desktop } // namespace UserInterface diff --git a/src/ui/audio/PlayQueue.hpp b/src/ui/audio/desktop/PlayQueue.hpp similarity index 98% rename from src/ui/audio/PlayQueue.hpp rename to src/ui/audio/desktop/PlayQueue.hpp index 72ef106a..9d150a41 100644 --- a/src/ui/audio/PlayQueue.hpp +++ b/src/ui/audio/desktop/PlayQueue.hpp @@ -28,6 +28,7 @@ #include "resource/CoverResource.hpp" namespace UserInterface { +namespace Desktop { class PlayQueueItemDelegate; class TrackSelector; @@ -86,7 +87,7 @@ class PlayQueue : public Wt::WTableView }; - +} // namespace Desktop } // namespace UserInterface #endif diff --git a/src/ui/audio/TableFilter.cpp b/src/ui/audio/desktop/TableFilter.cpp similarity index 99% rename from src/ui/audio/TableFilter.cpp rename to src/ui/audio/desktop/TableFilter.cpp index 56f0c48b..6b8f1010 100644 --- a/src/ui/audio/TableFilter.cpp +++ b/src/ui/audio/desktop/TableFilter.cpp @@ -28,6 +28,7 @@ #include "TableFilter.hpp" namespace UserInterface { +namespace Desktop { using namespace Database; @@ -253,5 +254,6 @@ TableFilterRelease::getConstraint(SearchFilter& filter) } } +} // namespace Desktop } // namespace UserInterface diff --git a/src/ui/audio/TableFilter.hpp b/src/ui/audio/desktop/TableFilter.hpp similarity index 98% rename from src/ui/audio/TableFilter.hpp rename to src/ui/audio/desktop/TableFilter.hpp index afac6866..d5bc6024 100644 --- a/src/ui/audio/TableFilter.hpp +++ b/src/ui/audio/desktop/TableFilter.hpp @@ -28,6 +28,7 @@ #include "database/DatabaseHandler.hpp" namespace UserInterface { +namespace Desktop { class TableFilterGenre : public Wt::WTableView, public Filter { @@ -111,6 +112,7 @@ class TableFilterRelease : public Wt::WTableView, public Filter }; +} // namespace Desktop } // namespace UserInterface #endif diff --git a/src/ui/audio/TrackView.cpp b/src/ui/audio/desktop/TrackView.cpp similarity index 98% rename from src/ui/audio/TrackView.cpp rename to src/ui/audio/desktop/TrackView.cpp index 75bb62b4..63c611b6 100644 --- a/src/ui/audio/TrackView.cpp +++ b/src/ui/audio/desktop/TrackView.cpp @@ -28,6 +28,7 @@ #include "TrackView.hpp" namespace UserInterface { +namespace Desktop { TrackView::TrackView( Database::Handler& db, Wt::WContainerWidget* parent) : Wt::WTableView( parent ), @@ -169,5 +170,6 @@ TrackView::getTracks(std::vector& trackIds) LMS_LOG(MOD_UI, SEV_DEBUG) << "Getting all tracks done!"; } +} // namespace Desktop } // namespace UserInterface diff --git a/src/ui/audio/TrackView.hpp b/src/ui/audio/desktop/TrackView.hpp similarity index 97% rename from src/ui/audio/TrackView.hpp rename to src/ui/audio/desktop/TrackView.hpp index cc8ae23f..77819bc8 100644 --- a/src/ui/audio/TrackView.hpp +++ b/src/ui/audio/desktop/TrackView.hpp @@ -28,6 +28,7 @@ #include "Filter.hpp" namespace UserInterface { +namespace Desktop { class TrackView : public Wt::WTableView, public Filter { @@ -69,6 +70,7 @@ class TrackView : public Wt::WTableView, public Filter }; +} // namespace Desktop } // namespace UserInterface #endif diff --git a/src/ui/audio/mobile/MobileAudio.cpp b/src/ui/audio/mobile/MobileAudio.cpp new file mode 100644 index 00000000..2431c8d5 --- /dev/null +++ b/src/ui/audio/mobile/MobileAudio.cpp @@ -0,0 +1,36 @@ +/* + * Copyright (C) 2015 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 "MobileAudio.hpp" + +namespace UserInterface { +namespace Mobile { + +Audio::Audio(Wt::WContainerWidget *parent) +: UserInterface::Audio(parent) +{ + new Wt::WText("Mobile version not implemented", this); +} + +} // namespace Mobile +} // namespace UserInterface + diff --git a/src/ui/audio/mobile/MobileAudio.hpp b/src/ui/audio/mobile/MobileAudio.hpp new file mode 100644 index 00000000..a36753fc --- /dev/null +++ b/src/ui/audio/mobile/MobileAudio.hpp @@ -0,0 +1,42 @@ +/* + * Copyright (C) 2015 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 UI_AUDIO_MOBILE_HPP +#define UI_AUDIO_MOBILE_HPP + +#include + +#include "audio/Audio.hpp" + +namespace UserInterface { +namespace Mobile { + +class Audio : public UserInterface::Audio +{ + public: + Audio(Wt::WContainerWidget *parent = 0); + + void search(std::string text) {}; + +}; + +} // namespace Mobile +} // namespace UserInterface + +#endif From 34714dd3c6108c63e21d76a5f761d83e3fe447c8 Mon Sep 17 00:00:00 2001 From: emeric Date: Sat, 17 Jan 2015 17:45:40 +0100 Subject: [PATCH 2/7] [UI] Cleaned up widget deletion --- src/ui/video/VideoWidget.cpp | 18 +++++------------- 1 file changed, 5 insertions(+), 13 deletions(-) diff --git a/src/ui/video/VideoWidget.cpp b/src/ui/video/VideoWidget.cpp index 566a392f..16ff470f 100644 --- a/src/ui/video/VideoWidget.cpp +++ b/src/ui/video/VideoWidget.cpp @@ -45,17 +45,6 @@ VideoWidget::search(const std::string& searchText) //TODO } -void -VideoWidget::backToList(void) -{ - if (_mediaPlayer) - delete _mediaPlayer; - - _videoDbWidget->setHidden(false); - -} - - void VideoWidget::playVideo(boost::filesystem::path p) { @@ -100,8 +89,11 @@ VideoWidget::playVideo(boost::filesystem::path p) parameters.setBitrate(Transcode::Stream::Audio, 0/*audioBitrate*/); parameters.setBitrate(Transcode::Stream::Video, 0/*videoBitrate*/); - _mediaPlayer = new VideoMediaPlayerWidget(parameters, this); - _mediaPlayer->close().connect(this, &VideoWidget::backToList); + VideoMediaPlayerWidget *mediaPlayer = new VideoMediaPlayerWidget(parameters, this); + mediaPlayer->close().connect(std::bind([=] () { + _videoDbWidget->setHidden(false); + delete mediaPlayer; + })); _videoDbWidget->setHidden(true); } From 7eba050af3c1e740174d59709d6ec6152dbe62b8 Mon Sep 17 00:00:00 2001 From: emeric Date: Fri, 23 Jan 2015 12:48:30 +0100 Subject: [PATCH 3/7] [UI] Added release name in the play queue --- src/ui/audio/PlayQueue.cpp | 22 ++++++++++++---------- 1 file changed, 12 insertions(+), 10 deletions(-) diff --git a/src/ui/audio/PlayQueue.cpp b/src/ui/audio/PlayQueue.cpp index b854cc84..e94b3f53 100644 --- a/src/ui/audio/PlayQueue.cpp +++ b/src/ui/audio/PlayQueue.cpp @@ -34,7 +34,7 @@ #include "PlayQueue.hpp" -static const int NameRole = Wt::UserRole; +static const int TrackInfoRole = Wt::UserRole; namespace { @@ -219,10 +219,11 @@ TrackSelector::setSize(std::size_t size) refreshPositions(); } -struct Name +struct TrackInfo { Wt::WString track; Wt::WString artist; + Wt::WString release; }; class PlayQueueItemDelegate : public Wt::WItemDelegate @@ -234,13 +235,13 @@ class PlayQueueItemDelegate : public Wt::WItemDelegate { Wt::WWidget* res; - if (!index.data(NameRole).empty()) + if (!index.data(TrackInfoRole).empty()) { - Name name = boost::any_cast(index.data(NameRole)); + TrackInfo trackInfo = boost::any_cast(index.data(TrackInfoRole)); Wt::WContainerWidget *container = new Wt::WContainerWidget(); - Wt::WText* track = new Wt::WText(name.track, Wt::PlainText, container); - Wt::WText* artist = new Wt::WText(name.artist, Wt::PlainText, container); + Wt::WText* track = new Wt::WText(trackInfo.track, Wt::PlainText, container); + Wt::WText* artist = new Wt::WText(trackInfo.artist + " - " + trackInfo.release, Wt::PlainText, container); artist->setInline(false); track->setInline(false); @@ -381,10 +382,11 @@ PlayQueue::addTracks(const std::vector& trackIds) coverUrl = "images/unknown-cover.jpg"; _model->setData(dataRow, COLUMN_ID_COVER, coverUrl, Wt::DecorationRole); - Name name; - name.track = Wt::WString::fromUTF8(track->getName()); - name.artist = Wt::WString::fromUTF8(track->getArtistName()); - _model->setData(dataRow, COLUMN_ID_NAME, name, NameRole); + TrackInfo trackInfo; + trackInfo.track = Wt::WString::fromUTF8(track->getName()); + trackInfo.artist = Wt::WString::fromUTF8(track->getArtistName()); + trackInfo.release = Wt::WString::fromUTF8(track->getReleaseName()); + _model->setData(dataRow, COLUMN_ID_NAME, trackInfo, TrackInfoRole); } } From f1189d215419ba44a7bd4cf49eb746a125c61b87 Mon Sep 17 00:00:00 2001 From: emeric Date: Wed, 4 Mar 2015 12:55:20 +0100 Subject: [PATCH 4/7] Updated documentation --- README | 20 +++++++++++++------- TODO | 6 ++++-- src/remote/proto/messages.proto | 10 ++++++++++ 3 files changed, 27 insertions(+), 9 deletions(-) diff --git a/README b/README index 2f3eb332..2cb78ce7 100644 --- a/README +++ b/README @@ -1,22 +1,22 @@ LMS - Lightweight Media Server -LMS is a free media streaming software, released under the GPLv3 license. +LMS is a self-hosted media streaming software, released under the GPLv3 license. It allows you to access your music and your videos using an https web interface. Features: +- Winamp-like interface, suited for large databases +- Audio/Video transcode for maximum interoperability and low bandwith requirements - User management -- Fast "Winamp-style" interface -- Audio/Video transcode (including downsampling), based on the libav project (see https://www.libav.org/). -- Custom API to access data using Protocol Buffers format over TCP/SSL (see https://github.com/google/protobuf/ and src/remote/proto) +- API available in Protocol Buffers (see src/remote/proto/messages.proto) Please note LMS is still under development and will gain more features in the future. -An android application is being developped as well. +A mobile application based on Kivy is being developped as well. LMS is written entirely in C++. Therefore, it is suitable to run on embedded devices, where space and/or memory are limited. Please note some media files may require significant CPU usage to be transcoded. -I) DEPENCIES +I) DEPENDENCIES Debian packages: g++ autoconf automake libboost-dev libavcodec-dev libwtdbosqlite-dev libwthttp-dev libwtdbo-dev libwt-dev libprotobuf-dev libconfig++-dev libprotobuf-dev protobuf-compiler libjpeg8-dev libavcodec-dev libavformat-dev libavutil-dev @@ -37,7 +37,7 @@ II) BUILD III) Setting up SSL materials (required by the SSL server and the web server) -This is just an example, you could do whatever you want (ca, self signed cert, etc.) +This is just a self signed certificate example, you could do use a CA if you want. Generate a dh file: # openssl dhparam -out dh2048.pem 2048 @@ -57,3 +57,9 @@ Create the lms.conf file using the etc/lms.sample.conf Depending on your SSL parameters, you may be asked for the PEM passphrase to unlock the private key. +V) LINKS + +- Wt (http://www.webtoolkit.eu/) +- bootstrap3 (http://getbootstrap.com/) +- libav project (https://www.libav.org/). +- Protocol Buffers (https://github.com/google/protobuf/) diff --git a/TODO b/TODO index d513cdf2..161330a4 100644 --- a/TODO +++ b/TODO @@ -22,7 +22,7 @@ - OGG metadata -> properly handle metadata nested in the audio stream [Transcode] -- some early end of playback spotted on flac files +- some early end of playback spotted on flac files. Spotted on old firefox versions? [UI] - handle internationalization @@ -31,6 +31,7 @@ - "signal not exposed" problem if a user logout and login again. bad resource destruction? [user/transcoding] - Prefered codecs for audio/video? + - use a slider instead of a combo box for the bitrates [admin/Users] - do not activate the update period/start time when 'admin' is checked [admin/Database update] @@ -39,10 +40,11 @@ [Audio] - Style eveything nicely... - - MediaPlayer: move slider using js (http://redmine.webtoolkit.eu/boards/2/topics/7924?r=8478) + - MediaPlayer: move the 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 covers in the release filter? + - Add a download button to get the current playlist in a streamed zip file [Video] - implement a decent mediaplayer diff --git a/src/remote/proto/messages.proto b/src/remote/proto/messages.proto index 30bef5f2..b9b2f704 100644 --- a/src/remote/proto/messages.proto +++ b/src/remote/proto/messages.proto @@ -5,6 +5,16 @@ import "media.proto"; package Remote; +// Every request sent by the client must be a ClientMessage +// The server will answer using a ServerMessage +// +// The client must authenticate first before querying data +// If an expected message is received, the connection is dropped by the server +// +// A header is to be added before the serialized protobuf message in order to keep +// track of where one message ends and the next begins. +// Format: +// MAGIC (4 bytes, 0xDE, 0xAD, 0xBE, 0xEF)- SIZE (4 bytes, big endian encoded) - serialized proto message message ClientMessage { enum Type From 0a98dabcd90664ee73f91f2a0a645141c9757b3e Mon Sep 17 00:00:00 2001 From: emeric Date: Wed, 4 Mar 2015 12:55:43 +0100 Subject: [PATCH 5/7] Corrected bad include path --- src/logger/Logger.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/logger/Logger.cpp b/src/logger/Logger.cpp index 828ab817..41b6db0f 100644 --- a/src/logger/Logger.cpp +++ b/src/logger/Logger.cpp @@ -23,7 +23,7 @@ #include #include #include -#include +#include #include #include #include @@ -89,6 +89,7 @@ Logger::init(const Config& config) boost::log::keywords::file_name = config.logPath + std::string(".%N"), boost::log::keywords::rotation_size = 10 * 1024 * 1024, boost::log::keywords::open_mode = std::ios_base::app, + boost::log::keywords::auto_flush = true, boost::log::keywords::format = ( boost::log::expressions::stream << boost::log::expressions::format_date_time< boost::posix_time::ptime >("TimeStamp", "[%Y-%m-%d %H:%M:%S]") From d2603da6de8b43472480b35ca41763534bb23aaf Mon Sep 17 00:00:00 2001 From: emeric Date: Wed, 4 Mar 2015 13:16:44 +0100 Subject: [PATCH 6/7] [UI] First working mobile version --- src/Makefile.am | 8 +- src/cover/CoverArt.cpp | 4 + src/cover/CoverArt.hpp | 2 +- src/cover/CoverArtGrabber.cpp | 22 ++- src/cover/CoverArtGrabber.hpp | 1 + src/database/Track.cpp | 12 +- src/database/Track.hpp | 1 + src/transcode/AvConvTranscoder.cpp | 4 +- src/transcode/AvConvTranscoder.hpp | 2 + src/transcode/Parameters.cpp | 2 +- src/ui/LmsApplication.cpp | 15 +- src/ui/approot/templates.xml | 66 ++++++- src/ui/audio/desktop/PlayQueue.cpp | 2 +- src/ui/audio/mobile/ArtistSearch.cpp | 111 +++++++++++ src/ui/audio/mobile/ArtistSearch.hpp | 58 ++++++ src/ui/audio/mobile/MobileAudio.cpp | 179 +++++++++++++++++- src/ui/audio/mobile/MobileAudio.hpp | 9 +- .../audio/mobile/MobileAudioMediaPlayer.cpp | 60 ++++++ .../audio/mobile/MobileAudioMediaPlayer.hpp | 50 +++++ src/ui/audio/mobile/ReleaseSearch.cpp | 120 ++++++++++++ src/ui/audio/mobile/ReleaseSearch.hpp | 65 +++++++ src/ui/audio/mobile/TrackSearch.cpp | 133 +++++++++++++ src/ui/audio/mobile/TrackSearch.hpp | 65 +++++++ src/ui/docroot/css/lms.css | 41 ++++ .../AvConvTranscodeStreamResource.cpp | 10 +- src/ui/resource/CoverResource.cpp | 76 +++++--- src/ui/resource/CoverResource.hpp | 5 + 27 files changed, 1071 insertions(+), 52 deletions(-) create mode 100644 src/ui/audio/mobile/ArtistSearch.cpp create mode 100644 src/ui/audio/mobile/ArtistSearch.hpp create mode 100644 src/ui/audio/mobile/MobileAudioMediaPlayer.cpp create mode 100644 src/ui/audio/mobile/MobileAudioMediaPlayer.hpp create mode 100644 src/ui/audio/mobile/ReleaseSearch.cpp create mode 100644 src/ui/audio/mobile/ReleaseSearch.hpp create mode 100644 src/ui/audio/mobile/TrackSearch.cpp create mode 100644 src/ui/audio/mobile/TrackSearch.hpp diff --git a/src/Makefile.am b/src/Makefile.am index 5507f6e3..3a3aa96d 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -40,14 +40,18 @@ lms_SOURCES = \ $(srcdir)/transcode/InputMediaFile.cpp \ $(srcdir)/ui/LmsApplication.cpp \ $(srcdir)/ui/auth/LmsAuth.cpp \ - $(srcdir)/ui/audio/desktop/DesktopAudio.cpp \ + $(srcdir)/ui/audio/desktop/DesktopAudio.cpp \ $(srcdir)/ui/audio/desktop/AudioMediaPlayer.cpp \ $(srcdir)/ui/audio/desktop/FilterChain.cpp \ $(srcdir)/ui/audio/desktop/KeywordSearchFilter.cpp \ $(srcdir)/ui/audio/desktop/PlayQueue.cpp \ $(srcdir)/ui/audio/desktop/TableFilter.cpp \ $(srcdir)/ui/audio/desktop/TrackView.cpp \ - $(srcdir)/ui/audio/mobile/MobileAudio.cpp \ + $(srcdir)/ui/audio/mobile/ArtistSearch.cpp \ + $(srcdir)/ui/audio/mobile/MobileAudio.cpp \ + $(srcdir)/ui/audio/mobile/MobileAudioMediaPlayer.cpp \ + $(srcdir)/ui/audio/mobile/ReleaseSearch.cpp \ + $(srcdir)/ui/audio/mobile/TrackSearch.cpp \ $(srcdir)/ui/common/DirectoryValidator.cpp \ $(srcdir)/ui/common/LineEdit.cpp \ $(srcdir)/ui/common/SessionData.cpp \ diff --git a/src/cover/CoverArt.cpp b/src/cover/CoverArt.cpp index 473fe3b5..35b2c991 100644 --- a/src/cover/CoverArt.cpp +++ b/src/cover/CoverArt.cpp @@ -49,6 +49,10 @@ CoverArt::scale(std::size_t size) boost::gil::read_image(iss, source, boost::gil::jpeg_tag()); } + if (source.width() == static_cast(size) + && source.height() == static_cast(size)) + return true; + // Resize boost::gil::resize_view(boost::gil::const_view(source), boost::gil::view(dest), diff --git a/src/cover/CoverArt.hpp b/src/cover/CoverArt.hpp index ccca8741..4594841c 100644 --- a/src/cover/CoverArt.hpp +++ b/src/cover/CoverArt.hpp @@ -32,7 +32,7 @@ class CoverArt public: typedef std::vector data_type; - CoverArt(); + CoverArt() {} CoverArt(const std::string& mime, const data_type& data) : _mimeType(mime), _data(data) {} const std::string& getMimeType() const { return _mimeType; } diff --git a/src/cover/CoverArtGrabber.cpp b/src/cover/CoverArtGrabber.cpp index 0a3fce62..ba2ced72 100644 --- a/src/cover/CoverArtGrabber.cpp +++ b/src/cover/CoverArtGrabber.cpp @@ -72,8 +72,6 @@ Grabber::getFromTrack(const boost::filesystem::path& p) return res; } - - std::vector Grabber::getFromTrack(Database::Track::pointer track) { @@ -96,5 +94,25 @@ Grabber::getFromTrack(Database::Track::pointer track) return res; } +std::vector +Grabber::getFromRelease(Wt::Dbo::Session& session, std::string releaseName) +{ + using namespace Database; + + // For now, just return the embedded cover of the first track + SearchFilter filter; + filter.exactMatch[SearchFilter::Field::Release].push_back(releaseName); + + Wt::Dbo::collection tracks + = Track::getAll(session, filter, -1, 1 /* limit result size */); + + Wt::Dbo::collection::iterator it = tracks.begin(); + + if (it != tracks.end()) + return getFromTrack(*it); + else + return std::vector(); +} + } // namespace CoverArt diff --git a/src/cover/CoverArtGrabber.hpp b/src/cover/CoverArtGrabber.hpp index e6c809a6..43555b7b 100644 --- a/src/cover/CoverArtGrabber.hpp +++ b/src/cover/CoverArtGrabber.hpp @@ -37,6 +37,7 @@ class Grabber static std::vector getFromInputFormatContext(const Av::InputFormatContext& input); static std::vector getFromTrack(Database::Track::pointer track); static std::vector getFromTrack(const boost::filesystem::path& path); + static std::vector getFromRelease(Wt::Dbo::Session& session, std::string releaseName); }; } // namespace CoverArt diff --git a/src/database/Track.cpp b/src/database/Track.cpp index 3701cb89..28e8f459 100644 --- a/src/database/Track.cpp +++ b/src/database/Track.cpp @@ -186,7 +186,7 @@ Track::getAllQuery(Wt::Dbo::Session& session, SearchFilter filter) SqlQuery sqlQuery = generatePartialQuery(filter); Wt::Dbo::Query query - = session.query( "SELECT t FROM track t " + sqlQuery.innerJoin().get() + " " + sqlQuery.where().get()).groupBy("t.id"); + = session.query( "SELECT t FROM track t " + sqlQuery.innerJoin().get() + " " + sqlQuery.where().get()).groupBy("t.id").orderBy("t.artist_name,t.date,t.release_name,t.disc_number,t.track_number"); BOOST_FOREACH(const std::string& bindArg, sqlQuery.where().getBindArgs()) query.bind(bindArg); @@ -200,6 +200,14 @@ Track::getAll(Wt::Dbo::Session& session, SearchFilter filter, int offset, int si return getAllQuery(session, filter).limit(size).offset(offset); } +std::vector +Track::getTracks(Wt::Dbo::Session& session, SearchFilter filter, int offset, int size) +{ + Wt::Dbo::collection< Track::pointer > tracks = getAll(session, filter, offset, size); + + return std::vector(tracks.begin(), tracks.end()); +} + Wt::Dbo::Query Track::getReleasesQuery(Wt::Dbo::Session& session, SearchFilter filter) { @@ -264,7 +272,7 @@ Track::updateArtistQueryModel(Wt::Dbo::Session& session, Wt::Dbo::QueryModel& model, SearchFilter filter, const std::vector& columnNames) { - Wt::Dbo::Query< pointer > query = getAllQuery(session, filter).orderBy("t.artist_name,t.date,t.release_name,t.disc_number,t.track_number"); + Wt::Dbo::Query< pointer > query = getAllQuery(session, filter); model.setQuery(query, columnNames.empty() ? true : false); // TODO do something better diff --git a/src/database/Track.hpp b/src/database/Track.hpp index 12274838..524ab4ec 100644 --- a/src/database/Track.hpp +++ b/src/database/Track.hpp @@ -111,6 +111,7 @@ class Track static Wt::Dbo::collection< pointer > getAll(Wt::Dbo::Session& session); // Used for remote static Wt::Dbo::collection< pointer > getAll(Wt::Dbo::Session& session, SearchFilter filter, int offset = -1, int size = -1); + static std::vector getTracks(Wt::Dbo::Session& session, SearchFilter filter, int offset = -1, int size = -1); static std::vector getReleases(Wt::Dbo::Session& session, SearchFilter filter, int offset = -1, int size = -1); static std::vector getArtists(Wt::Dbo::Session& session, SearchFilter filter, int offset = -1, int size = -1); diff --git a/src/transcode/AvConvTranscoder.cpp b/src/transcode/AvConvTranscoder.cpp index 8700ed20..1b4dd815 100644 --- a/src/transcode/AvConvTranscoder.cpp +++ b/src/transcode/AvConvTranscoder.cpp @@ -70,7 +70,8 @@ AvConvTranscoder::AvConvTranscoder(const Parameters& parameters) _source(_outputPipe.source, boost::iostreams::close_handle), _is(_source), _in(&_is), - _isComplete(false) + _isComplete(false), + _outputBytes(0) { if (!boost::filesystem::exists(_parameters.getInputMediaFile().getPath())) { @@ -179,6 +180,7 @@ AvConvTranscoder::process(std::vector& output, std::size_t maxSiz while(readDataSize < maxSize && _in && _in.get(ch)) { output.push_back(ch); readDataSize++; + _outputBytes++; // stats } if (!_in || _in.fail() || _in.eof()) { diff --git a/src/transcode/AvConvTranscoder.hpp b/src/transcode/AvConvTranscoder.hpp index e3663809..2209b819 100644 --- a/src/transcode/AvConvTranscoder.hpp +++ b/src/transcode/AvConvTranscoder.hpp @@ -50,6 +50,7 @@ class AvConvTranscoder void process(std::vector& output, std::size_t maxSize); bool isComplete(void) const { return _isComplete;}; + std::size_t getOutputBytes(void) const { return _outputBytes; } private: @@ -72,6 +73,7 @@ class AvConvTranscoder static boost::filesystem::path _avConvPath; bool _isComplete; + std::size_t _outputBytes; // Bytes produced so far }; } // naspace Transcode diff --git a/src/transcode/Parameters.cpp b/src/transcode/Parameters.cpp index e129dbac..3d35591f 100644 --- a/src/transcode/Parameters.cpp +++ b/src/transcode/Parameters.cpp @@ -72,7 +72,7 @@ Parameters::getOutputBitrate(Stream::Type type) const { std::map::const_iterator it = _outputBitrate.find(type); - if (it != _inputStreams.end()) + if (it != _outputBitrate.end()) { return it->second; } diff --git a/src/ui/LmsApplication.cpp b/src/ui/LmsApplication.cpp index 052a1908..7aaaebc1 100644 --- a/src/ui/LmsApplication.cpp +++ b/src/ui/LmsApplication.cpp @@ -50,8 +50,11 @@ namespace { bool agentIsMobile() { const Wt::WEnvironment& env = Wt::WApplication::instance()->environment(); - - return (env.agentIsIEMobile() || env.agentIsMobileWebKit()); + return (env.agentIsIEMobile() + || env.agentIsMobileWebKit() + || env.userAgent().find("Mobile") != std::string::npos // Workaround for firefox + || env.userAgent().find("Tablet") != std::string::npos // Workaround for firefox + ); } } @@ -139,7 +142,7 @@ LmsApplication::handleAuthEvent(void) { Wt::Auth::User user(_sessionData.getDatabaseHandler().getLogin().user()); - LMS_LOG(MOD_UI, SEV_NOTICE) << "User '" << user.identity(Wt::Auth::Identity::LoginName) << "' logged in"; + LMS_LOG(MOD_UI, SEV_NOTICE) << "User '" << user.identity(Wt::Auth::Identity::LoginName) << "' logged in from '" << Wt::WApplication::instance()->environment().clientAddress() << "', user agent = " << Wt::WApplication::instance()->environment().agent() << ", session = " << Wt::WApplication::instance()->sessionId(); this->root()->setOverflow(Wt::WContainerWidget::OverflowHidden); @@ -164,9 +167,9 @@ LmsApplication::handleAuthEvent(void) Audio *audio; if (agentIsMobile()) - audio = new Desktop::Audio(_sessionData); + audio = new Mobile::Audio(_sessionData.getDatabaseHandler()); else - audio = new Mobile::Audio(); + audio = new Desktop::Audio(_sessionData); VideoWidget *videoWidget = new VideoWidget(_sessionData); @@ -210,7 +213,7 @@ LmsApplication::handleAuthEvent(void) } else { - LMS_LOG(MOD_UI, SEV_NOTICE) << "User logged out"; + LMS_LOG(MOD_UI, SEV_NOTICE) << "User logged out, session = " << Wt::WApplication::instance()->sessionId(); quit(""); redirect("/"); diff --git a/src/ui/approot/templates.xml b/src/ui/approot/templates.xml index 4a635a85..c4f1bf71 100644 --- a/src/ui/approot/templates.xml +++ b/src/ui/approot/templates.xml @@ -318,6 +318,68 @@ + +
+
${search}
+
+
+ + +
+
+
${text}
+
+
+
+ +
+
+
${text}
+
+
+
+ + +
+
${name}
+
+
+ + +
+
+
+
+
+
${cover}
+
${name}
+
+
+
+
+
+
+ + +
+
+
+
+
+
${cover}
+
${name}
+
${btn}
+
+
+
+
+
+
+ + +
+
${player}
+
+
+ - - diff --git a/src/ui/audio/desktop/PlayQueue.cpp b/src/ui/audio/desktop/PlayQueue.cpp index 62552bee..721813f9 100644 --- a/src/ui/audio/desktop/PlayQueue.cpp +++ b/src/ui/audio/desktop/PlayQueue.cpp @@ -377,7 +377,7 @@ PlayQueue::addTracks(const std::vector& trackIds) std::string coverUrl; if (track->hasCover()) - coverUrl = _coverResource->url() + "&coverid=" + Wt::asString(track.id()).toUTF8(); + coverUrl = _coverResource->getTrackUrl(track.id()); else coverUrl = "images/unknown-cover.jpg"; _model->setData(dataRow, COLUMN_ID_COVER, coverUrl, Wt::DecorationRole); diff --git a/src/ui/audio/mobile/ArtistSearch.cpp b/src/ui/audio/mobile/ArtistSearch.cpp new file mode 100644 index 00000000..bbdb4d40 --- /dev/null +++ b/src/ui/audio/mobile/ArtistSearch.cpp @@ -0,0 +1,111 @@ +/* + * Copyright (C) 2015 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 "ArtistSearch.hpp" + +namespace UserInterface { +namespace Mobile { + +ArtistSearch::ArtistSearch(Database::Handler& db, Wt::WContainerWidget *parent) +: Wt::WContainerWidget(parent), +_db(db), +_resCount(0) +{ + Wt::WTemplate *title = new Wt::WTemplate(this); + title->setTemplateText(Wt::WString::tr("mobile-search-title")); + + title->bindWidget("text", new Wt::WText("Artists", Wt::PlainText)); +} + +void +ArtistSearch::clear() +{ + while (count() > 1) + removeWidget(this->widget(1)); + + _resCount = 0; +} + +void +ArtistSearch::search(Database::SearchFilter filter, size_t nb) +{ + clear(); + addResults(filter, nb); +} + +void +ArtistSearch::addResults(Database::SearchFilter filter, std::size_t nb) +{ + + std::vector artists; + + { + Wt::Dbo::Transaction transaction(_db.getSession()); + + // Request one more to see if more results are to be expected + artists = Database::Track::getArtists(_db.getSession(), filter, _resCount, nb + 1); + } + + bool expectMoreResults; + if (artists.size() == nb + 1) + { + expectMoreResults = true; + artists.pop_back(); + } + else + expectMoreResults = false; + + BOOST_FOREACH(std::string artist, artists) + { + Wt::WTemplate* res = new Wt::WTemplate(this); + res->setTemplateText(Wt::WString::tr("mobile-artist-res")); + + Wt::WText *text = new Wt::WText(Wt::WString::fromUTF8(artist), Wt::PlainText); + res->bindWidget("name", text); + + res->clicked().connect(std::bind([=] { + _sigArtistSelected(artist); + })); + } + + _resCount += artists.size();; + + if (expectMoreResults) + { + Wt::WTemplate* moreRes = new Wt::WTemplate(this); + moreRes->setTemplateText(Wt::WString::tr("mobile-search-more")); + + moreRes->bindWidget("text", new Wt::WText("Tap to show more results...")); + + moreRes->clicked().connect(std::bind([=] { + _sigMoreArtistsSelected(); + removeWidget(moreRes); + + addResults(filter, 20); + })); + } + +} + +} // namespace Mobile +} // namespace UserInterface diff --git a/src/ui/audio/mobile/ArtistSearch.hpp b/src/ui/audio/mobile/ArtistSearch.hpp new file mode 100644 index 00000000..937b89bb --- /dev/null +++ b/src/ui/audio/mobile/ArtistSearch.hpp @@ -0,0 +1,58 @@ +/* + * Copyright (C) 2015 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 UI_MOBILE_ARTIST_SEARCH_HPP +#define UI_MOBILE_ARTIST_SEARCH_HPP + +#include +#include + +#include "database/DatabaseHandler.hpp" + +namespace UserInterface { +namespace Mobile { + +class ArtistSearch : public Wt::WContainerWidget +{ + public: + + ArtistSearch(Database::Handler& db, Wt::WContainerWidget *parent = 0); + + void search(Database::SearchFilter filter, std::size_t nb); + + // Slots + Wt::Signal& artistSelected() { return _sigArtistSelected;} + Wt::Signal& moreArtistsSelected() { return _sigMoreArtistsSelected;} + + private: + + Wt::Signal _sigArtistSelected; + Wt::Signal _sigMoreArtistsSelected; + + void clear(void); + void addResults(Database::SearchFilter filter, size_t nb); + + Database::Handler& _db; + std::size_t _resCount; +}; + +} // namespace Mobile +} // namespace UserInterface + +#endif diff --git a/src/ui/audio/mobile/MobileAudio.cpp b/src/ui/audio/mobile/MobileAudio.cpp index 2431c8d5..c5d5c87b 100644 --- a/src/ui/audio/mobile/MobileAudio.cpp +++ b/src/ui/audio/mobile/MobileAudio.cpp @@ -17,18 +17,191 @@ * along with LMS. If not, see . */ +#include +#include +#include + #include +#include +#include +#include #include +#include #include "MobileAudio.hpp" +#include "ArtistSearch.hpp" +#include "ReleaseSearch.hpp" +#include "TrackSearch.hpp" +#include "MobileAudioMediaPlayer.hpp" + +#include "logger/Logger.hpp" + namespace UserInterface { namespace Mobile { -Audio::Audio(Wt::WContainerWidget *parent) -: UserInterface::Audio(parent) +Audio::Audio(Database::Handler& db, Wt::WContainerWidget *parent) +: UserInterface::Audio(parent), +_db(db) { - new Wt::WText("Mobile version not implemented", this); + // Root div has to be a "container" + this->setStyleClass("container"); + + Wt::WTemplate* search = new Wt::WTemplate(this); + search->setTemplateText(Wt::WString::tr("mobile-search")); + + Wt::WLineEdit *edit = new Wt::WLineEdit(); + edit->setEmptyText("Search..."); + + search->bindWidget("search", edit); + search->setMargin(10); + + ArtistSearch* artistSearch = new ArtistSearch(_db, this); + ReleaseSearch* releaseSearch = new ReleaseSearch(_db, this); + TrackSearch* trackSearch = new TrackSearch(_db, this); + + Wt::WTemplate* playerTemplate = new Wt::WTemplate(this); + playerTemplate->setTemplateText(Wt::WString::tr("mobile-audio-player")); + + AudioMediaPlayer* mediaPlayer = new AudioMediaPlayer(); + playerTemplate->bindWidget("player", mediaPlayer); + + edit->changed().connect(std::bind([=] () { + std::string text = edit->text().toUTF8(); + + // When a new search is done, output some results from: + // Artist + // Release + // Song + std::vector keywords; + boost::algorithm::split(keywords, text, boost::is_any_of(" "), boost::token_compress_on); + + { + Database::SearchFilter filter; + BOOST_FOREACH(std::string keyword, keywords) + { + Database::SearchFilter::FieldValues likeMatch; + + likeMatch[Database::SearchFilter::Field::Artist].push_back(keyword); + likeMatch[Database::SearchFilter::Field::Release].push_back(keyword); + filter.likeMatches.push_back(likeMatch); + } + + releaseSearch->search(filter, 3); + } + + { + Database::SearchFilter filter; + BOOST_FOREACH(std::string keyword, keywords) + { + Database::SearchFilter::FieldValues likeMatch; + + likeMatch[Database::SearchFilter::Field::Artist].push_back(keyword); + + filter.likeMatches.push_back(likeMatch); + } + + artistSearch->search(filter, 3); + } + + { + Database::SearchFilter filter; + BOOST_FOREACH(std::string keyword, keywords) + { + Database::SearchFilter::FieldValues likeMatch; + + likeMatch[Database::SearchFilter::Field::Track].push_back(keyword); + + filter.likeMatches.push_back(likeMatch); + } + + trackSearch->search(filter, 3); + } + + artistSearch->show(); + releaseSearch->show(); + trackSearch->show(); + + })); + + artistSearch->moreArtistsSelected().connect(std::bind([=] { + releaseSearch->hide(); + trackSearch->hide(); + artistSearch->show(); + })); + + artistSearch->artistSelected().connect(std::bind([=] (std::string artist) { + artistSearch->hide(); + trackSearch->hide(); + releaseSearch->show(); + + Database::SearchFilter filter; + filter.exactMatch[Database::SearchFilter::Field::Artist].push_back(artist); + + releaseSearch->search(filter, 20); + }, std::placeholders::_1)); + + releaseSearch->moreReleasesSelected().connect(std::bind([=] { + artistSearch->hide(); + trackSearch->hide(); + releaseSearch->show(); + })); + + releaseSearch->releaseSelected().connect(std::bind([=] (std::string release) + { + artistSearch->hide(); + releaseSearch->hide(); + trackSearch->show(); + + // TODO load track search with selected release + Database::SearchFilter filter; + filter.exactMatch[Database::SearchFilter::Field::Release].push_back(release); + + trackSearch->search(filter, 20); + }, std::placeholders::_1)); + + trackSearch->moreTracksSelected().connect(std::bind([=] + { + artistSearch->hide(); + releaseSearch->hide(); + })); + + trackSearch->trackPlay().connect(std::bind([=] (Database::Track::id_type id) + { + LMS_LOG(MOD_UI, SEV_DEBUG) << "Playing track id " << id; + Wt::Dbo::Transaction transaction(_db.getSession()); + + Database::Track::pointer track = Database::Track::getById(_db.getSession(), id); + + if (track) + { + // 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; + } + // TODO compute parameters using user s profile + Transcode::InputMediaFile inputFile(track->getPath()); + Transcode::Parameters parameters(inputFile, Transcode::Format::get(encoding)); + parameters.setBitrate(Transcode::Stream::Audio, 96000); + + mediaPlayer->play(parameters); + } + } , std::placeholders::_1)); + + // Initially, populate the widgets using an empty search + { + Database::SearchFilter filter; // empty filter + artistSearch->search(filter, 3); + releaseSearch->search(filter, 3); + trackSearch->search(filter, 3); + } + } } // namespace Mobile diff --git a/src/ui/audio/mobile/MobileAudio.hpp b/src/ui/audio/mobile/MobileAudio.hpp index a36753fc..143159ad 100644 --- a/src/ui/audio/mobile/MobileAudio.hpp +++ b/src/ui/audio/mobile/MobileAudio.hpp @@ -22,6 +22,8 @@ #include +#include "database/DatabaseHandler.hpp" + #include "audio/Audio.hpp" namespace UserInterface { @@ -30,10 +32,13 @@ namespace Mobile { class Audio : public UserInterface::Audio { public: - Audio(Wt::WContainerWidget *parent = 0); + Audio(Database::Handler& db, Wt::WContainerWidget *parent = 0); - void search(std::string text) {}; + void search(std::string text) {} + private: + + Database::Handler& _db; }; } // namespace Mobile diff --git a/src/ui/audio/mobile/MobileAudioMediaPlayer.cpp b/src/ui/audio/mobile/MobileAudioMediaPlayer.cpp new file mode 100644 index 00000000..d459a8bc --- /dev/null +++ b/src/ui/audio/mobile/MobileAudioMediaPlayer.cpp @@ -0,0 +1,60 @@ +/* + * Copyright (C) 2015 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 "MobileAudioMediaPlayer.hpp" + +#include "resource/AvConvTranscodeStreamResource.hpp" + +namespace UserInterface { +namespace Mobile { + +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) +{ + _player = new Wt::WMediaPlayer(Wt::WMediaPlayer::Audio, this); + _player->addSource( getEncoding(), "" ); +} + +void +AudioMediaPlayer::play(const Transcode::Parameters& parameters) +{ + AvConvTranscodeStreamResource *resource = new AvConvTranscodeStreamResource( parameters, this ); + + _player->clearSources(); + _player->addSource( getEncoding(), Wt::WLink(resource)); + + _player->play(); +} + +} // namespace UserInterface +} // namespace Mobile diff --git a/src/ui/audio/mobile/MobileAudioMediaPlayer.hpp b/src/ui/audio/mobile/MobileAudioMediaPlayer.hpp new file mode 100644 index 00000000..bcc77753 --- /dev/null +++ b/src/ui/audio/mobile/MobileAudioMediaPlayer.hpp @@ -0,0 +1,50 @@ +/* + * Copyright (C) 2015 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 UI_AUDIO_MOBILE_MEDIA_PLAYER_HPP +#define UI_AUDIO_MOBILE_MEDIA_PLAYER_HPP + +#include +#include + +#include "transcode/Parameters.hpp" + +namespace UserInterface { +namespace Mobile { + +class AudioMediaPlayer : public Wt::WContainerWidget +{ + public: + + static Wt::WMediaPlayer::Encoding getEncoding(); + + AudioMediaPlayer(Wt::WContainerWidget *parent = 0); + + void play(const Transcode::Parameters& parameters); + + private: + + Wt::WMediaPlayer *_player; + +}; + +} // namespace UserInterface +} // namespace Mobile + +#endif diff --git a/src/ui/audio/mobile/ReleaseSearch.cpp b/src/ui/audio/mobile/ReleaseSearch.cpp new file mode 100644 index 00000000..0e2d39ac --- /dev/null +++ b/src/ui/audio/mobile/ReleaseSearch.cpp @@ -0,0 +1,120 @@ +/* + * Copyright (C) 2015 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 "logger/Logger.hpp" + +#include "ReleaseSearch.hpp" + +namespace UserInterface { +namespace Mobile { + +ReleaseSearch::ReleaseSearch(Database::Handler& db, Wt::WContainerWidget *parent) +: Wt::WContainerWidget(parent), +_db(db), +_resCount(0) +{ + Wt::WTemplate* title = new Wt::WTemplate(this); + title->setTemplateText(Wt::WString::tr("mobile-search-title")); + + title->bindWidget("text", new Wt::WText("Releases", Wt::PlainText)); + + _coverResource = new CoverResource(db, 56); +} + +void +ReleaseSearch::clear() +{ + while (count() > 1) + removeWidget(this->widget(1)); + + _resCount = 0; +} + +void +ReleaseSearch::search(Database::SearchFilter filter, size_t max) +{ + clear(); + addResults(filter, max); +} + +void +ReleaseSearch::addResults(Database::SearchFilter filter, size_t nb) +{ + + std::vector releases; + + { + Wt::Dbo::Transaction transaction(_db.getSession()); + + // Request one more to see if more results are to be expected + releases = Database::Track::getReleases(_db.getSession(), filter, _resCount, nb + 1); + } + + bool expectMoreResults; + if (releases.size() == nb + 1) + { + expectMoreResults = true; + releases.pop_back(); + } + else + expectMoreResults = false; + + BOOST_FOREACH(std::string release, releases) + { + Wt::WTemplate* releaseWidget = new Wt::WTemplate(this); + releaseWidget->setTemplateText(Wt::WString::tr("mobile-release-res")); + + Wt::WImage *cover = new Wt::WImage(); + cover->setStyleClass("center-block"); + cover->setImageLink( Wt::WLink( _coverResource->getReleaseUrl(release))); + releaseWidget->bindWidget("cover", cover); + + releaseWidget->bindWidget("name", new Wt::WText(Wt::WString::fromUTF8(release), Wt::PlainText)); + + releaseWidget->clicked().connect(std::bind([=] { + _sigReleaseSelected(release); + })); + + } + + _resCount += releases.size();; + + if (expectMoreResults) + { + Wt::WTemplate* moreRes = new Wt::WTemplate(this); + moreRes->setTemplateText(Wt::WString::tr("mobile-search-more")); + + moreRes->bindWidget("text", new Wt::WText("Tap to show more results...")); + + moreRes->clicked().connect(std::bind([=] { + _sigMoreReleasesSelected(); + removeWidget(moreRes); + + addResults(filter, 20); + })); + } +} + +} // namespace Mobile +} // namespace UserInterface diff --git a/src/ui/audio/mobile/ReleaseSearch.hpp b/src/ui/audio/mobile/ReleaseSearch.hpp new file mode 100644 index 00000000..97f6e4bb --- /dev/null +++ b/src/ui/audio/mobile/ReleaseSearch.hpp @@ -0,0 +1,65 @@ +/* + * Copyright (C) 2015 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 UI_MOBILE_RELEASE_SEARCH_HPP +#define UI_MOBILE_RELEASE_SEARCH_HPP + +#include + +#include +#include + +#include "resource/CoverResource.hpp" + +#include "database/DatabaseHandler.hpp" + +namespace UserInterface { +namespace Mobile { + +class ReleaseSearch : public Wt::WContainerWidget +{ + public: + + ReleaseSearch(Database::Handler& db, Wt::WContainerWidget *parent = 0); + + void search(Database::SearchFilter filter, size_t nb); + + // Slots + Wt::Signal& releaseSelected() { return _sigReleaseSelected;} + Wt::Signal& moreReleasesSelected() { return _sigMoreReleasesSelected;} + + private: + + Wt::Signal _sigReleaseSelected; + Wt::Signal _sigMoreReleasesSelected; + + void clear(void); + void addResults(Database::SearchFilter filter, size_t nb); + + Database::Handler& _db; + + CoverResource* _coverResource; + + std::size_t _resCount; +}; + +} // namespace Mobile +} // namespace UserInterface + +#endif diff --git a/src/ui/audio/mobile/TrackSearch.cpp b/src/ui/audio/mobile/TrackSearch.cpp new file mode 100644 index 00000000..c6c5c46d --- /dev/null +++ b/src/ui/audio/mobile/TrackSearch.cpp @@ -0,0 +1,133 @@ +/* + * Copyright (C) 2015 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 "logger/Logger.hpp" + +#include "TrackSearch.hpp" + +namespace UserInterface { +namespace Mobile { + +TrackSearch::TrackSearch(Database::Handler& db, Wt::WContainerWidget *parent) +: Wt::WContainerWidget(parent), +_db(db), +_resCount(0) +{ + Wt::WTemplate* title = new Wt::WTemplate(this); + title->setTemplateText(Wt::WString::tr("mobile-search-title")); + + title->bindWidget("text", new Wt::WText("Tracks", Wt::PlainText)); + + _coverResource = new CoverResource(db, 56); +} + +void +TrackSearch::clear() +{ + while (count() > 1) + removeWidget(this->widget(1)); + + _resCount = 0; +} + +void +TrackSearch::search(Database::SearchFilter filter, size_t max) +{ + clear(); + addResults(filter, max); +} + +void +TrackSearch::addResults(Database::SearchFilter filter, size_t nb) +{ + Wt::Dbo::Transaction transaction(_db.getSession()); + + std::vector< Database::Track::pointer > tracks = Database::Track::getTracks(_db.getSession(), filter, _resCount, nb + 1); + + bool expectMoreResults; + if (tracks.size() == nb + 1) + { + expectMoreResults = true; + tracks.pop_back(); + } + else + expectMoreResults = false; + + BOOST_FOREACH(Database::Track::pointer track, tracks) + { + Wt::WTemplate* trackWidget = new Wt::WTemplate(this); + trackWidget->setTemplateText(Wt::WString::tr("mobile-track-res")); + + Wt::WImage *cover = new Wt::WImage(); + cover->setStyleClass("center-block"); + cover->setImageLink( Wt::WLink (_coverResource->getTrackUrl(track.id())) ); + trackWidget->bindWidget("cover", cover); + + // Track Name (bold) + // Artist - Album (italic) + Wt::WContainerWidget *container = new Wt::WContainerWidget(); + Wt::WText *title = new Wt::WText(Wt::WString::fromUTF8(track->getName()), Wt::PlainText); + title->setStyleClass("mobile-track"); + container->addWidget(title); + + if (!track->getArtistName().empty() + || !track->getReleaseName().empty()) + { + title->setInline(false); + Wt::WText *artistRelease = new Wt::WText(Wt::WString::fromUTF8(track->getArtistName() + " - " + track->getReleaseName()), Wt::PlainText); + artistRelease->setStyleClass("mobile-artist"); + container->addWidget(artistRelease); + } + trackWidget->bindWidget("name", container); + + Wt::WPushButton *playBtn = new Wt::WPushButton("Play"); + playBtn->setStyleClass("btn-primary center-block"); + playBtn->clicked().connect(std::bind([=] { + _sigTrackPlay.emit(track.id()); + })); + + trackWidget->bindWidget("btn", playBtn); + + } + + _resCount += tracks.size(); + + if (expectMoreResults) + { + Wt::WTemplate* moreRes = new Wt::WTemplate(this); + moreRes->setTemplateText(Wt::WString::tr("mobile-search-more")); + + moreRes->bindWidget("text", new Wt::WText("Tap to show more results...")); + + moreRes->clicked().connect(std::bind([=] { + _sigMoreTracksSelected(); + removeWidget(moreRes); + + addResults(filter, 20); + })); + } +} + +} // namespace Mobile +} // namespace UserInterface diff --git a/src/ui/audio/mobile/TrackSearch.hpp b/src/ui/audio/mobile/TrackSearch.hpp new file mode 100644 index 00000000..16076df2 --- /dev/null +++ b/src/ui/audio/mobile/TrackSearch.hpp @@ -0,0 +1,65 @@ +/* + * Copyright (C) 2015 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 UI_MOBILE_TRACK_SEARCH_HPP +#define UI_MOBILE_TRACK_SEARCH_HPP + +#include + +#include +#include + +#include "resource/CoverResource.hpp" + +#include "database/DatabaseHandler.hpp" + +namespace UserInterface { +namespace Mobile { + +class TrackSearch : public Wt::WContainerWidget +{ + public: + + TrackSearch(Database::Handler& db, Wt::WContainerWidget *parent = 0); + + void search(Database::SearchFilter filter, size_t nb); + + // Slots + Wt::Signal& trackPlay() { return _sigTrackPlay;} + Wt::Signal& moreTracksSelected() { return _sigMoreTracksSelected;} + + private: + + Wt::Signal _sigTrackPlay; + Wt::Signal _sigMoreTracksSelected; + + void clear(void); + void addResults(Database::SearchFilter filter, size_t nb); + + Database::Handler& _db; + + CoverResource* _coverResource; + + std::size_t _resCount; +}; + +} // namespace Mobile +} // namespace UserInterface + +#endif diff --git a/src/ui/docroot/css/lms.css b/src/ui/docroot/css/lms.css index 23c78024..68c40fa1 100644 --- a/src/ui/docroot/css/lms.css +++ b/src/ui/docroot/css/lms.css @@ -55,3 +55,44 @@ div.contents { font-weight: bold; } +.mobile-search-title { + font-weight: bold; + height: 32px; + background-color: grey; + color: white; + text-align: center; +} + +.mobile-search-entry { + min-height: 64px; + border-bottom: 1px solid lightgray; + overflow: hidden; + text-overflow: ellipsis; +} + +.mobile-search-entry:active { + background-color: lightgrey; +} + +.mobile-search-more { + height: 64px; + border-bottom: 1px solid lightgray; +} + +.mobile-search-more:active { + background-color: lightgrey; +} + +.mobile-track { + font-weight: bold; +} + +.mobile-artist { + font-style: italic; +} + +.vertical-align { + display: flex; + align-items: center; +} + diff --git a/src/ui/resource/AvConvTranscodeStreamResource.cpp b/src/ui/resource/AvConvTranscodeStreamResource.cpp index a8e22ea1..025455a2 100644 --- a/src/ui/resource/AvConvTranscodeStreamResource.cpp +++ b/src/ui/resource/AvConvTranscodeStreamResource.cpp @@ -44,11 +44,11 @@ void AvConvTranscodeStreamResource::handleRequest(const Wt::Http::Request& request, Wt::Http::Response& response) { - static const std::size_t chunkSize = 8192; // TODO parametrize? - // see if this request is for a continuation: Wt::Http::ResponseContinuation *continuation = request.continuation(); + LMS_LOG(MOD_UI, SEV_DEBUG) << "Handling new request. Continuation = " << std::boolalpha << continuation; + std::shared_ptr transcoder; if (continuation) transcoder = boost::any_cast >(continuation->data()); @@ -65,13 +65,15 @@ AvConvTranscodeStreamResource::handleRequest(const Wt::Http::Request& request, if (!transcoder->isComplete()) { std::vector data; - data.reserve(chunkSize); + data.reserve(_bufferSize); - transcoder->process(data, chunkSize); + transcoder->process(data, _bufferSize); // Give the client all the output data response.out().write(reinterpret_cast(&data[0]), data.size()); + LMS_LOG(MOD_UI, SEV_DEBUG) << "Written " << data.size() << " bytes! complete = " << std::boolalpha << transcoder->isComplete() << ", produced bytes = " << transcoder->getOutputBytes(); + if (!response.out()) LMS_LOG(MOD_UI, SEV_ERROR) << "Write failed!"; } diff --git a/src/ui/resource/CoverResource.cpp b/src/ui/resource/CoverResource.cpp index 5af7ed2e..6866512e 100644 --- a/src/ui/resource/CoverResource.cpp +++ b/src/ui/resource/CoverResource.cpp @@ -35,6 +35,21 @@ CoverResource::CoverResource(Database::Handler& db, std::size_t size, Wt::WObjec _db(db), _size(size) { + // Load default cover art + + std::vector data; + + { + std::ifstream ist(Wt::WApplication::instance()->docRoot() + "/images/unknown-cover.jpg"); + char c; + while(ist.get(c)) + data.push_back(c); + } + + _defaultCover.setData(data); + _defaultCover.setMimeType("image/jpeg"); + + _defaultCover.scale(size); } CoverResource:: ~CoverResource() @@ -42,55 +57,66 @@ CoverResource:: ~CoverResource() beingDeleted(); } +std::string +CoverResource::getReleaseUrl(std::string releaseName) +{ + return url() + "&release=" + releaseName; +} + +std::string +CoverResource::getTrackUrl(Database::Track::id_type trackId) +{ + return url()+ "&trackid=" + std::to_string(trackId); +} void CoverResource::handleRequest(const Wt::Http::Request& request, Wt::Http::Response& response) { // Get the id of the track - const std::string *trackIdStr = request.getParameter("coverid"); + const std::string *trackIdStr = request.getParameter("trackid"); + const std::string *releaseStr = request.getParameter("release"); + + std::vector covers; if (trackIdStr) { Database::Track::id_type trackId; { - std::istringstream iss(*trackIdStr); iss >> trackId; + std::istringstream iss(*trackIdStr); + iss >> trackId; } Wt::Dbo::Transaction transaction(_db.getSession()); Database::Track::pointer track = Database::Track::getById(_db.getSession(), trackId); - std::vector covers = CoverArt::Grabber::getFromTrack(track); + covers = CoverArt::Grabber::getFromTrack(track); transaction.commit(); + } + else if (releaseStr) + { + Wt::Dbo::Transaction transaction(_db.getSession()); + covers = CoverArt::Grabber::getFromRelease(_db.getSession(), *releaseStr); + } - BOOST_FOREACH(CoverArt::CoverArt& cover, covers) + BOOST_FOREACH(CoverArt::CoverArt& cover, covers) + { + if (cover.scale(_size)) { - if (cover.scale(_size)) - { - response.setMimeType( cover.getMimeType() ); + response.setMimeType( cover.getMimeType() ); - BOOST_FOREACH(unsigned char c, cover.getData()) - response.out().put( c ); - - return; - } - else - LMS_LOG(MOD_UI, SEV_DEBUG) << "Resize error for track id = " << trackId; - } - LMS_LOG(MOD_UI, SEV_DEBUG) << "no valid cover found for track id = " << trackId; - { - std::ifstream ist(Wt::WApplication::instance()->docRoot() + "/images/unknown-cover.jpg"); - - char c; - while(ist.get(c)) + BOOST_FOREACH(unsigned char c, cover.getData()) response.out().put( c ); - response.setMimeType("image/jpeg"); + return; } } - else - LMS_LOG(MOD_UI, SEV_DEBUG) << "no cover id parameter"; + + // If no cover found, just send default one + response.setMimeType( _defaultCover.getMimeType() ); + BOOST_FOREACH(unsigned char c, _defaultCover.getData()) + response.out().put( c ); + } - } // namespace UserInterface diff --git a/src/ui/resource/CoverResource.hpp b/src/ui/resource/CoverResource.hpp index 59efde51..9436899f 100644 --- a/src/ui/resource/CoverResource.hpp +++ b/src/ui/resource/CoverResource.hpp @@ -26,6 +26,7 @@ #include #include "database/DatabaseHandler.hpp" +#include "cover/CoverArt.hpp" namespace UserInterface { @@ -37,12 +38,16 @@ class CoverResource : public Wt::WResource Wt::WObject *parent = 0); ~CoverResource(); + std::string getReleaseUrl(std::string releaseName); + std::string getTrackUrl(Database::Track::id_type trackId); + void handleRequest(const Wt::Http::Request& request, Wt::Http::Response& response); private: Database::Handler& _db; std::size_t _size; + CoverArt::CoverArt _defaultCover; }; } // namespace UserInterface From d15a569baa680eff0621ec65379a361b943da411 Mon Sep 17 00:00:00 2001 From: emeric Date: Thu, 5 Mar 2015 13:51:22 +0100 Subject: [PATCH 7/7] Updated TODO --- TODO | 1 + 1 file changed, 1 insertion(+) diff --git a/TODO b/TODO index 161330a4..e8d4e07d 100644 --- a/TODO +++ b/TODO @@ -45,6 +45,7 @@ - TrackView : Reselect the current selected item when displaying the updated search results - Add covers in the release filter? - Add a download button to get the current playlist in a streamed zip file + - Add keyboard shortcuts [Video] - implement a decent mediaplayer