Merge branch 'develop'

This commit is contained in:
emeric
2016-04-09 15:53:22 +02:00
60 changed files with 2073 additions and 991 deletions
+2 -1
View File
@@ -13,7 +13,8 @@ lms_css_DATA = \
docroot/css/lms.css
lms_images_DATA = \
docroot/images/unknown-cover.jpg
docroot/images/unknown-cover.jpg \
docroot/images/unknown-artist.jpg
lms_approot_DATA = \
approot/templates.xml
+17 -7
View File
@@ -4,13 +4,19 @@ 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
- Winamp-like interface, suited for large media collections
- Audio/Video transcode for maximum interoperability and low bandwith requirements
- User management
- Multi genre
- MusicBrainzID support
Please note LMS is still under development and will gain more features in the future.
Please note LMS is still under development and will gain more features in the future:
- Radio function (using AcousticBrainz)
- Interface suited for mobile devices
- Video support
- Subsonic and/or Ampache API
LMS is written entirely in C++. Therefore, it is suitable to run on embedded devices, where space and/or memory are limited.
LMS is written entirely in C++. Therefore, it is suitable to run on embedded devices, where space and memory are limited.
Please note some media files may require significant CPU usage to be transcoded.
## Dependencies
@@ -42,13 +48,15 @@ $ make install
This command requires root privileges
## Running
Here is a command line example to run on port 5081:
```sh
$ /usr/bin/lms --docroot='/usr/share/lms/docroot/;/resources,/css,/images' --approot=/usr/share/lms/approot --http-address 0.0.0.0 --http-port 5081
$ /usr/bin/lms --docroot='/usr/share/lms/docroot/;/resources,/css,/images,/favicon.ico' --approot=/usr/share/lms/approot --http-address 0.0.0.0 --http-port 5081
```
It is highly recommended to run LMS as a non root user.
The exectuable needs write accesses to the /var/lms/ directory.
To connect to LMS, just open your favorite browser and go to http://localhost:5081
## Mobile
Add the following code in your wt_config.xml file:
@@ -69,12 +77,14 @@ Generate a self signed certificate:
```sh
$ openssl req -x509 -out cert.pem -keyout privkey.pem -newkey rsa:4096
```
Run:
Run (on port 5081):
```sh
$ /usr/bin/lms --docroot='/usr/share/lms/docroot/;/resources,/css,/images' --approot=/usr/share/lms/approot --https-address 0.0.0.0 --https-port 5081 --ssl-certificate cert.pem --ssl-private-key privkey.pem --ssl-tmp-dh dh2048.pem
$ /usr/bin/lms --docroot='/usr/share/lms/docroot/;/resources,/css,/images,/favicon.ico' --approot=/usr/share/lms/approot --https-address 0.0.0.0 --https-port 5081 --ssl-certificate cert.pem --ssl-private-key privkey.pem --ssl-tmp-dh dh2048.pem
```
Depending on your SSL parameters, you may be asked for the PEM passphrase to unlock the private key.
To connect to LMS, just open your favorite browser and go to https://localhost:5081
## Credits
- Wt (http://www.webtoolkit.eu/)
+1 -1
View File
@@ -53,9 +53,9 @@
- Perform a search for each key pressed (using a timer, in JS)
- Save user prefs in local storage (volume, loop, shuffle, current playqueue, etc.)
[desktop]
- Better style...
- TrackView: Reselect the current selected item when displaying the updated search results
- Add keyboard shortcuts
- playqueue: try to get a single div to display the cover / artist / track name in order to improve style
[mobile]
- Reorganize the views to ease internal paths
- Covers not always the same height
+68 -89
View File
@@ -356,21 +356,6 @@
</div>
</message>
<message id="mediaplayer-controls">
<div class="btn-group" role="group" aria-label="...">
${prev}
${play}
${pause}
${next}
</div>
</message>
<message id="mobile-search">
<div class="row">
<div class="col-xs-12">${search}</div>
</div>
</message>
<message id="mobile-search-title">
<div class="row">
<div class="col-xs-12">
@@ -386,108 +371,79 @@
</div>
</message>
<message id="mobile-artist-res">
<div class="row">
<div class="col-xs-12">
<div class="mobile-search-entry">${name}</div>
</div>
</div>
</message>
<message id="mobile-release-res">
<div class="row">
<div class="col-xs-12">
<div class="mobile-search-entry">
<div class="row">
<div class ="vertical-align">
<div class="col-xs-2" style="margin-top:3px">${cover}</div>
<div class="col-xs-10">${name}</div>
</div>
</div>
</div>
</div>
</div>
</message>
<message id="mobile-track-res">
<div class="row">
<div class="col-xs-12">
<div class="mobile-search-entry">
<div class="row">
<div class="vertical-align">
<div class="col-xs-2" style="margin-top:3px">${cover}</div>
<div class="col-xs-7">${name}</div>
<div class="col-xs-3">${btn}</div>
</div>
</div>
</div>
</div>
</div>
</message>
<message id="mobile-audio-footer">
<nav class="navbar navbar-default navbar-fixed-bottom" role="navigation">
${player}
</nav>
</message>
<message id="wa-release-wrapper">
<div class="section text-center">
<div class="container">
<div class="row">
${contents}
</div>
</div>
</div>
<!--
wa-artist-search
-> wa-artist-search-res
-> wa-artist-search-res
-> ...
-->
<message id="wa-artist-search">
${title}
${contents}
${show-more}
</message>
<message id="wa-release-res">
<div class="col-xs-6 col-md-2">
<br/>${cover}
<h4 class="release_truncated-name" title="${release_name}">${name}<div style="font-size: 75%"><br/>${artist}<br/></div></h4>
</div>
</message>
<message id="wa-artist-wrapper">
<div class="section text-center">
<div class="container">
<div class="row">
${contents}
</div>
</div>
</div>
</message>
<message id="wa-artist-res">
<message id="wa-artist-search-res">
<div class="col-xs-6 col-md-2">
<br/>${gif}
<h4 class="release_truncated-name" title="${artistname}">${name}<div style="font-size: 75%"></div></h4>
</div>
</message>
<message id="wa-track-wrapper">
<!--
wa-release-search
-> wa-release-search-res
-> wa-release-search-res
-> ...
-->
<message id="wa-release-search">
${title}
${contents}
${show-more}
</message>
<message id="wa-release-search-res">
<div class="col-xs-6 col-md-2">
<br/>${cover}
<h4 class="release_truncated-name" title="${release_name}">${name}<div style="font-size: 75%"><br/>${artist}<br/></div></h4>
</div>
</message>
<!--
wa-track-search
-> wa-track-search-res
-> wa-track-search-res
-> ...
!-->
<message id="wa-track-search">
${title}
<div class="menu-category list-group ">
${track-container}
${contents}
</div>
${show-more}
</message>
<message id="wa-track">
<message id="wa-track-search-res">
<div class="menu-item list-group-item ">
<div class="row">
<div class="vertical-align">
<div class="col-xs-2" style="margin-top:3px">${cover}</div>
<div class="col-xs-8">${artist-name}<br/>${track-name}</div>
<div class="col-xs-2" style="margin-top:3px"><span class="badge">${btn}</span></div>
<div class="col-xs-2">${play-btn}${add-btn}</div>
</div>
</div>
</div>
</message>
<!-- Track container
<!-- Release view
Track bar title
Release1
@@ -525,13 +481,36 @@
</message>
<message id="wa-trackview-track">
<div class="menu-item list-group-item ">${<if-has-disc-num>}${disc-num}-${</if-has-disc-num>}${<if-has-track-num>}${track-num}. ${</if-has-track-num>}${track-name} <span class="badge">${btn}</span><span class="badge">${time}</span></div>
<div class="menu-item list-group-item ">${<if-has-disc-num>}${disc-num}-${</if-has-disc-num>}${<if-has-track-num>}${track-num}. ${</if-has-track-num>}${track-name} ${play-btn}${add-btn}</div>
</message>
<!-- Playqueue view
!-->
<message id="wa-playqueue-view">
${controls}
<div class="menu-category list-group">
${contents}
</div>
</message>
<message id="wa-playqueue-controls">
${load}${save}${clear}${shuffle}${repeat}
</message>
<message id="wa-playqueue-track">
<div class="row">
<div class="vertical-align">
<div class="col-xs-2" style="margin-top:3px">${cover}</div>
<div class="col-xs-8">${artist-name}<br/>${track-name}</div>
<div class="col-xs-2" style="margin-top:3px">${play-btn}${del-btn}</div>
</div>
</div>
</message>
<message id="wa-audio-player">
${audio}
${prev}${play-pause}${next}
<div class="mediaplayer-volume-container visible-lg">
<div class="mediaplayer-volume-container hidden-xs">
${volume}
</div>
<div class="mediaplayer-cover hidden-xs">
@@ -545,7 +524,7 @@
${curtime}${seekbar}${duration}
</div>
</div>
${shuffle}${repeat}
${<if-has-shuffle>}${shuffle}${</if-has-shuffle>}${<if-has-repeat>}${repeat}${</if-has-repeat>}${<if-has-playqueue>}${playqueue}${</if-has-playqueue>}
</message>
</messages>
+23
View File
@@ -129,6 +129,19 @@
writing-mode: bt-lr;
}
.mobile-btn {
margin: 8px;
}
.mobile-btn-active {
color: #428bca;
}
.mobile-btn i {
cursor: pointer;
text-shadow: 0px 2px 3px rgba(0, 0, 0, 0.4);
}
.mobile-search-title {
font-weight: bold;
height: 32px;
@@ -198,3 +211,13 @@
max-width: 100%;
height: auto;
}
.release-search-cover {
width: 512px;
min-width: 512px;
height: 512px;
min-height: 512px;
display: flex;
justify-content: space-between;
align-items: center;
}
BIN
View File
Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

Regular → Executable
BIN
View File
Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.1 KiB

After

Width:  |  Height:  |  Size: 25 KiB

+8 -2
View File
@@ -32,15 +32,21 @@ lms_SOURCES = \
$(srcdir)/ui/audio/desktop/TableFilter.cpp \
$(srcdir)/ui/audio/desktop/TrackView.cpp \
$(srcdir)/ui/audio/mobile/ArtistSearch.cpp \
$(srcdir)/ui/audio/mobile/ArtistSearchView.cpp \
$(srcdir)/ui/audio/mobile/ArtistView.cpp \
$(srcdir)/ui/audio/mobile/MobileAudio.cpp \
$(srcdir)/ui/audio/mobile/MobilePlayQueue.cpp \
$(srcdir)/ui/audio/mobile/PreviewSearchView.cpp \
$(srcdir)/ui/audio/mobile/ReleaseSearch.cpp \
$(srcdir)/ui/audio/mobile/TrackReleaseView.cpp \
$(srcdir)/ui/audio/mobile/ReleaseSearchView.cpp \
$(srcdir)/ui/audio/mobile/ReleaseView.cpp \
$(srcdir)/ui/audio/mobile/TrackSearch.cpp \
$(srcdir)/ui/audio/mobile/TrackSearchView.cpp \
$(srcdir)/ui/common/DirectoryValidator.cpp \
$(srcdir)/ui/common/InputRange.cpp \
$(srcdir)/ui/common/LineEdit.cpp \
$(srcdir)/ui/resource/AvConvTranscodeStreamResource.cpp \
$(srcdir)/ui/resource/CoverResource.cpp \
$(srcdir)/ui/resource/ImageResource.cpp \
$(srcdir)/ui/resource/TranscodeResource.cpp \
$(srcdir)/ui/settings/Settings.cpp \
$(srcdir)/ui/settings/SettingsAccountFormView.cpp \
+4 -4
View File
@@ -96,15 +96,16 @@ std::vector<boost::filesystem::path>
Grabber::getCoverPaths(const boost::filesystem::path& directoryPath, std::size_t nbMaxCovers) const
{
std::vector<boost::filesystem::path> res;
boost::system::error_code ec;
// TODO handle preferred file names
boost::filesystem::directory_iterator itPath(directoryPath);
boost::filesystem::directory_iterator itPath(directoryPath, ec);
boost::filesystem::directory_iterator itEnd;
while (itPath != itEnd)
while (!ec && itPath != itEnd)
{
boost::filesystem::path path = *itPath;
itPath++;
itPath.increment(ec);
if (!boost::filesystem::is_regular(path))
continue;
@@ -122,7 +123,6 @@ Grabber::getCoverPaths(const boost::filesystem::path& directoryPath, std::size_t
if (res.size() >= nbMaxCovers)
break;
}
return res;
}
+234 -191
View File
@@ -23,6 +23,7 @@
#include <boost/asio/placeholders.hpp>
#include "logger/Logger.hpp"
#include "utils/Utils.hpp"
#include "database/Types.hpp"
@@ -244,7 +245,10 @@ Updater::process(boost::system::error_code err)
LMS_LOG(DBUPDATER, INFO) << "Processing root directory '" << rootDirectory.path << "' DONE";
}
LMS_LOG(DBUPDATER, INFO) << "Scan complete. Changes = " << stats.nbChanges() << ", Errors = " << stats.nbScanErrors;
if (_running)
checkDuplicatedAudioFiles(stats);
LMS_LOG(DBUPDATER, INFO) << "Scan complete. Scanned = " << stats.nbScanned << ", Skipped = " << stats.nbSkipped << ", Changes = " << stats.nbChanges() << " (added = " << stats.nbAdded << ", nbRemoved = " << stats.nbRemoved << ", nbModified = " << stats.nbModified << "), Scan errors = " << stats.nbScanErrors << ", Not imported = " << stats.nbNotImported;
// Update database stats
boost::posix_time::ptime now = boost::posix_time::second_clock::local_time();
@@ -387,12 +391,26 @@ Updater::processAudioFile( const boost::filesystem::path& file, Stats& stats)
Wt::Dbo::ptr<Track> track = Track::getByPath(_db.getSession(), file);
if (track && track->getLastWriteTime() == lastWriteTime)
{
stats.nbSkipped++;
transaction.rollback();
return;
}
transaction.rollback();
}
MetaData::Items items;
if (!_metadataParser.parse(file, items))
{
stats.nbScanErrors++;
return;
}
stats.nbScanned++;
std::vector<unsigned char> checksum ;
computeCrc(file, checksum);
Wt::Dbo::Transaction transaction(_db.getSession());
@@ -404,7 +422,7 @@ Updater::processAudioFile( const boost::filesystem::path& file, Stats& stats)
if (items.find(MetaData::Type::AudioStreams) == items.end()
|| boost::any_cast<std::vector<MetaData::AudioStream> >(items[MetaData::Type::AudioStreams]).empty())
{
LMS_LOG(DBUPDATER, DEBUG) << "Skipped '" << file << "' (no audio stream found)";
LMS_LOG(DBUPDATER, INFO) << "Skipped '" << file << "' (no audio stream found)";
// If Track exists here, delete it!
if (track)
@@ -412,12 +430,13 @@ Updater::processAudioFile( const boost::filesystem::path& file, Stats& stats)
track.remove();
stats.nbRemoved++;
}
stats.nbNotImported++;
return;
}
if (items.find(MetaData::Type::Duration) == items.end()
|| boost::any_cast<boost::posix_time::time_duration>(items[MetaData::Type::Duration]).total_seconds() <= 0)
{
LMS_LOG(DBUPDATER, DEBUG) << "Skipped '" << file << "' (no duration or duration <= 0)";
LMS_LOG(DBUPDATER, INFO) << "Skipped '" << file << "' (no duration or duration <= 0)";
// If Track exists here, delete it!
if (track)
@@ -425,171 +444,178 @@ Updater::processAudioFile( const boost::filesystem::path& file, Stats& stats)
track.remove();
stats.nbRemoved++;
}
stats.nbNotImported++;
return;
}
// ***** Title
std::string title;
if (items.find(MetaData::Type::Title) != items.end()) {
title = boost::any_cast<std::string>(items[MetaData::Type::Title]);
}
else
if (items.find(MetaData::Type::Title) != items.end())
{
title = boost::any_cast<std::string>(items[MetaData::Type::Title]);
}
else
{
// TODO parse file name guess track etc.
// For now juste use file name as title
title = file.filename().string();
}
// ***** Genres
std::vector< Genre::pointer > genres;
{
std::list<std::string> genreList;
if (items.find(MetaData::Type::Genres) != items.end())
genreList = boost::any_cast< std::list<std::string> > (items[MetaData::Type::Genres]);
genres = getGenres( genreList );
}
assert( !genres.empty() );
// ***** Artist
Artist::pointer artist;
{
std::string artistName;
std::string artistMusicBrainzID;
if (items.find(MetaData::Type::MusicBrainzArtistID) != items.end())
artistMusicBrainzID = boost::any_cast<std::string>(items[MetaData::Type::MusicBrainzArtistID] );
if (items.find(MetaData::Type::Artist) != items.end())
artistName = boost::any_cast<std::string>(items[MetaData::Type::Artist]);
artist = getArtist(file, artistName, artistMusicBrainzID);
}
assert(artist);
// ***** Release
Release::pointer release;
{
std::string releaseName;
std::string releaseMusicBrainzID;
if (items.find(MetaData::Type::MusicBrainzAlbumID) != items.end())
releaseMusicBrainzID = boost::any_cast<std::string>(items[MetaData::Type::MusicBrainzAlbumID] );
if (items.find(MetaData::Type::Album) != items.end())
releaseName = boost::any_cast<std::string>(items[MetaData::Type::Album]);
release = getRelease(file, releaseName, releaseMusicBrainzID);
}
assert(release);
// If file already exist, update data
// Otherwise, create it
if (!track)
{
// Create a new song
track = Track::create(_db.getSession(), file);
LMS_LOG(DBUPDATER, INFO) << "Adding '" << file << "'";
stats.nbAdded++;
}
else
{
LMS_LOG(DBUPDATER, INFO) << "Updating '" << file << "'";
stats.nbModified++;
}
assert(track);
track.modify()->setChecksum(checksum);
track.modify()->setArtist(artist);
track.modify()->setRelease(release);
track.modify()->setLastWriteTime(lastWriteTime);
track.modify()->setName(title);
track.modify()->setDuration( boost::any_cast<boost::posix_time::time_duration>(items[MetaData::Type::Duration]) );
track.modify()->setAddedTime( boost::posix_time::second_clock::local_time() );
{
std::string trackGenreList;
// Product genre list
for (Genre::pointer genre : genres)
{
// TODO parse file name guess track etc.
// For now juste use file name as title
title = file.filename().string();
if (!trackGenreList.empty())
trackGenreList += ", ";
trackGenreList += genre->getName();
}
track.modify()->setGenres( trackGenreList );
}
track.modify()->setGenres( genres );
// ***** Genres
std::vector< Genre::pointer > genres;
{
std::list<std::string> genreList;
if (items.find(MetaData::Type::TrackNumber) != items.end())
track.modify()->setTrackNumber( boost::any_cast<std::size_t>(items[MetaData::Type::TrackNumber]) );
if (items.find(MetaData::Type::Genres) != items.end())
genreList = boost::any_cast< std::list<std::string> > (items[MetaData::Type::Genres]);
if (items.find(MetaData::Type::TotalTrack) != items.end())
track.modify()->setTotalTrackNumber( boost::any_cast<std::size_t>(items[MetaData::Type::TotalTrack]) );
genres = getGenres( genreList );
}
assert( !genres.empty() );
if (items.find(MetaData::Type::DiscNumber) != items.end())
track.modify()->setDiscNumber( boost::any_cast<std::size_t>(items[MetaData::Type::DiscNumber]) );
// ***** Artist
Artist::pointer artist;
{
std::string artistName;
std::string artistMusicBrainzID;
if (items.find(MetaData::Type::TotalDisc) != items.end())
track.modify()->setTotalDiscNumber( boost::any_cast<std::size_t>(items[MetaData::Type::TotalDisc]) );
if (items.find(MetaData::Type::MusicBrainzArtistID) != items.end())
artistMusicBrainzID = boost::any_cast<std::string>(items[MetaData::Type::MusicBrainzArtistID] );
if (items.find(MetaData::Type::Date) != items.end())
track.modify()->setDate( boost::any_cast<boost::posix_time::ptime>(items[MetaData::Type::Date]) );
if (items.find(MetaData::Type::Artist) != items.end())
artistName = boost::any_cast<std::string>(items[MetaData::Type::Artist]);
if (items.find(MetaData::Type::OriginalDate) != items.end())
{
track.modify()->setOriginalDate( boost::any_cast<boost::posix_time::ptime>(items[MetaData::Type::OriginalDate]) );
artist = getArtist(file, artistName, artistMusicBrainzID);
}
assert(artist);
// If a file has an OriginalDate but no date, set the date to ease filtering
if (items.find(MetaData::Type::Date) == items.end())
track.modify()->setDate( boost::any_cast<boost::posix_time::ptime>(items[MetaData::Type::OriginalDate]) );
}
// ***** Release
Release::pointer release;
{
std::string releaseName;
std::string releaseMusicBrainzID;
if (items.find(MetaData::Type::MusicBrainzTrackID) != items.end())
{
track.modify()->setMBID( boost::any_cast<std::string>(items[MetaData::Type::MusicBrainzTrackID]) );
}
if (items.find(MetaData::Type::MusicBrainzAlbumID) != items.end())
releaseMusicBrainzID = boost::any_cast<std::string>(items[MetaData::Type::MusicBrainzAlbumID] );
if (items.find(MetaData::Type::HasCover) != items.end())
{
bool hasCover = boost::any_cast<bool>(items[MetaData::Type::HasCover]);
if (items.find(MetaData::Type::Album) != items.end())
releaseName = boost::any_cast<std::string>(items[MetaData::Type::Album]);
track.modify()->setCoverType( hasCover ? Track::CoverType::Embedded : Track::CoverType::None );
}
release = getRelease(file, releaseName, releaseMusicBrainzID);
}
assert(release);
// If file already exist, update data
// Otherwise, create it
if (!track)
{
// Create a new song
track = Track::create(_db.getSession(), file);
LMS_LOG(DBUPDATER, INFO) << "Adding '" << file << "'";
stats.nbAdded++;
}
else
{
LMS_LOG(DBUPDATER, INFO) << "Updating '" << file << "'";
stats.nbModified++;
}
assert(track);
track.modify()->setArtist(artist);
track.modify()->setRelease(release);
track.modify()->setLastWriteTime(lastWriteTime);
track.modify()->setName(title);
track.modify()->setDuration( boost::any_cast<boost::posix_time::time_duration>(items[MetaData::Type::Duration]) );
track.modify()->setAddedTime( boost::posix_time::second_clock::local_time() );
{
std::string trackGenreList;
// Product genre list
for (Genre::pointer genre : genres)
{
if (!trackGenreList.empty())
trackGenreList += ", ";
trackGenreList += genre->getName();
}
track.modify()->setGenres( trackGenreList );
}
track.modify()->setGenres( genres );
if (items.find(MetaData::Type::TrackNumber) != items.end())
track.modify()->setTrackNumber( boost::any_cast<std::size_t>(items[MetaData::Type::TrackNumber]) );
if (items.find(MetaData::Type::TotalTrack) != items.end())
track.modify()->setTotalTrackNumber( boost::any_cast<std::size_t>(items[MetaData::Type::TotalTrack]) );
if (items.find(MetaData::Type::DiscNumber) != items.end())
track.modify()->setDiscNumber( boost::any_cast<std::size_t>(items[MetaData::Type::DiscNumber]) );
if (items.find(MetaData::Type::TotalDisc) != items.end())
track.modify()->setTotalDiscNumber( boost::any_cast<std::size_t>(items[MetaData::Type::TotalDisc]) );
if (items.find(MetaData::Type::Date) != items.end())
track.modify()->setDate( boost::any_cast<boost::posix_time::ptime>(items[MetaData::Type::Date]) );
if (items.find(MetaData::Type::OriginalDate) != items.end())
{
track.modify()->setOriginalDate( boost::any_cast<boost::posix_time::ptime>(items[MetaData::Type::OriginalDate]) );
// If a file has an OriginalDate but no date, set the date to ease filtering
if (items.find(MetaData::Type::Date) == items.end())
track.modify()->setDate( boost::any_cast<boost::posix_time::ptime>(items[MetaData::Type::OriginalDate]) );
}
if (items.find(MetaData::Type::HasCover) != items.end())
{
bool hasCover = boost::any_cast<bool>(items[MetaData::Type::HasCover]);
track.modify()->setCoverType( hasCover ? Track::CoverType::Embedded : Track::CoverType::None );
}
transaction.commit();
transaction.commit();
}
void
void
Updater::processRootDirectory(RootDirectory rootDirectory, Stats& stats)
{
boost::system::error_code ec;
if (!boost::filesystem::exists(rootDirectory.path) || !boost::filesystem::is_directory(rootDirectory.path))
return;
boost::filesystem::recursive_directory_iterator itPath(rootDirectory.path, ec);
boost::filesystem::recursive_directory_iterator itPath(rootDirectory.path);
boost::filesystem::recursive_directory_iterator itEnd;
while (itPath != itEnd)
while (!ec && itPath != itEnd)
{
boost::filesystem::path path = *itPath;
itPath.increment(ec);
if (!_running)
return;
if (boost::filesystem::is_regular(*itPath))
if (boost::filesystem::is_regular(path))
{
switch( rootDirectory.type )
{
case Database::MediaDirectory::Audio:
if (isFileSupported(*itPath, _audioFileExtensions))
processAudioFile( *itPath, stats );
if (isFileSupported(path, _audioFileExtensions))
processAudioFile(path, stats );
break;
case Database::MediaDirectory::Video:
if (isFileSupported(*itPath, _videoFileExtensions))
processVideoFile( *itPath, stats);
if (isFileSupported(path, _videoFileExtensions))
processVideoFile(path, stats);
break;
}
}
++itPath;
}
}
@@ -714,6 +740,29 @@ Updater::checkAudioFiles( Stats& stats )
LMS_LOG(DBUPDATER, INFO) << "Check audio files done!";
}
void
Updater::checkDuplicatedAudioFiles(Stats& stats)
{
LMS_LOG(DBUPDATER, INFO) << "Checking duplicated audio files";
Wt::Dbo::Transaction transaction(_db.getSession());
std::vector<Track::pointer> tracks = Database::Track::getMBIDDuplicates(_db.getSession());
for (Track::pointer track : tracks)
{
LMS_LOG(DBUPDATER, INFO) << "Found duplicated MBID [" << track->getMBID() << "], file: " << track->getPath() << " - " << track->getArtist()->getName() << " - " << track->getName();
}
tracks = Database::Track::getChecksumDuplicates(_db.getSession());
for (Track::pointer track : tracks)
{
LMS_LOG(DBUPDATER, INFO) << "Found duplicated checksum [" << bufferToString(track->getChecksum()) << "], file: " << track->getPath() << " - " << track->getArtist()->getName() << " - " << track->getName();
}
LMS_LOG(DBUPDATER, INFO) << "Checking duplicated audio files done!";
}
void
Updater::checkVideoFiles( Stats& stats )
{
@@ -741,80 +790,74 @@ Updater::checkVideoFiles( Stats& stats )
LMS_LOG(DBUPDATER, DEBUG) << "Check video files done!";
}
void
Updater::processVideoFile( const boost::filesystem::path& file, Stats& stats)
{
try {
// Check last update time
boost::posix_time::ptime lastWriteTime (boost::posix_time::from_time_t( boost::filesystem::last_write_time( file ) ) );
// Check last update time
boost::posix_time::ptime lastWriteTime (boost::posix_time::from_time_t( boost::filesystem::last_write_time( file ) ) );
Wt::Dbo::Transaction transaction(_db.getSession());
Wt::Dbo::Transaction transaction(_db.getSession());
// Skip file if last write is the same
Wt::Dbo::ptr<Video> video = Video::getByPath(_db.getSession(), file);
if (video && video->getLastWriteTime() == lastWriteTime)
return;
// Skip file if last write is the same
Wt::Dbo::ptr<Video> video = Video::getByPath(_db.getSession(), file);
if (video && video->getLastWriteTime() == lastWriteTime)
return;
MetaData::Items items;
if (!_metadataParser.parse(file, items))
return;
MetaData::Items items;
_metadataParser.parse(file, items);
// We estimate this is a video if:
// - we found a least one video stream
// - the duration is not null
if (items.find(MetaData::Type::VideoStreams) == items.end()
|| boost::any_cast<std::vector<MetaData::VideoStream> >(items[MetaData::Type::VideoStreams]).empty())
{
LMS_LOG(DBUPDATER, ERROR) << "Skipped '" << file << "' (no video stream found)";
// If the video exists here, delete it!
if (video) {
video.remove();
stats.nbRemoved++;
}
return;
}
if (items.find(MetaData::Type::Duration) == items.end()
|| boost::any_cast<boost::posix_time::time_duration>(items[MetaData::Type::Duration]).total_seconds() == 0)
{
LMS_LOG(DBUPDATER, ERROR) << "Skipped '" << file << "' (no duration or duration 0)";
// If Track exists here, delete it!
if (video) {
video.remove();
stats.nbRemoved++;
}
return;
}
// If video already exist, update data
// Otherwise, create it
// Today we are very aggressive, but we could also guess names from path, etc.
if (!video)
{
video = Video::create(_db.getSession(), file);
LMS_LOG(DBUPDATER, DEBUG) << "Adding '" << file << "'";
stats.nbAdded++;
}
else
{
LMS_LOG(DBUPDATER, DEBUG) << "Updating '" << file << "'";
stats.nbModified++;
}
assert(video);
video.modify()->setName( file.filename().string() );
video.modify()->setDuration( boost::any_cast<boost::posix_time::time_duration>(items[MetaData::Type::Duration]) );
video.modify()->setLastWriteTime(lastWriteTime);
transaction.commit();
}
catch( std::exception& e )
// We estimate this is a video if:
// - we found a least one video stream
// - the duration is not null
if (items.find(MetaData::Type::VideoStreams) == items.end()
|| boost::any_cast<std::vector<MetaData::VideoStream> >(items[MetaData::Type::VideoStreams]).empty())
{
LMS_LOG(DBUPDATER, ERROR) << "Exception while parsing video file : '" << file << "': '" << e.what() << "' => skipping!";
stats.nbScanErrors++;
LMS_LOG(DBUPDATER, ERROR) << "Skipped '" << file << "' (no video stream found)";
// If the video exists here, delete it!
if (video) {
video.remove();
stats.nbRemoved++;
}
return;
}
if (items.find(MetaData::Type::Duration) == items.end()
|| boost::any_cast<boost::posix_time::time_duration>(items[MetaData::Type::Duration]).total_seconds() == 0)
{
LMS_LOG(DBUPDATER, ERROR) << "Skipped '" << file << "' (no duration or duration 0)";
// If Track exists here, delete it!
if (video) {
video.remove();
stats.nbRemoved++;
}
return;
}
// If video already exist, update data
// Otherwise, create it
// Today we are very aggressive, but we could also guess names from path, etc.
if (!video)
{
video = Video::create(_db.getSession(), file);
LMS_LOG(DBUPDATER, DEBUG) << "Adding '" << file << "'";
stats.nbAdded++;
}
else
{
LMS_LOG(DBUPDATER, DEBUG) << "Updating '" << file << "'";
stats.nbModified++;
}
assert(video);
video.modify()->setName( file.filename().string() );
video.modify()->setDuration( boost::any_cast<boost::posix_time::time_duration>(items[MetaData::Type::Duration]) );
video.modify()->setLastWriteTime(lastWriteTime);
transaction.commit();
}
} // namespace DatabaseUpdater
+5 -1
View File
@@ -44,10 +44,13 @@ class Updater
struct Stats
{
std::size_t nbSkipped = 0; // no change since last scan
std::size_t nbScanned = 0;
std::size_t nbScanErrors = 0; // cannot scan file
std::size_t nbNotImported = 0; // Not imported (criteria not filled)
std::size_t nbAdded = 0;
std::size_t nbRemoved = 0;
std::size_t nbModified = 0;
std::size_t nbScanErrors = 0;
std::size_t nbChanges() const { return nbAdded + nbRemoved + nbModified;}
};
@@ -84,6 +87,7 @@ class Updater
// Audio
void checkAudioFiles( Stats& stats );
void checkDuplicatedAudioFiles( Stats& stats );
void processAudioFile( const boost::filesystem::path& file, Stats& stats);
// Video
+24
View File
@@ -45,6 +45,12 @@ Artist::getByMBID(Wt::Dbo::Session& session, const std::string& mbid)
return session.find<Artist>().where("mbid = ?").bind(mbid);
}
Artist::pointer
Artist::getById(Wt::Dbo::Session& session, Artist::id_type id)
{
return session.find<Artist>().where("id = ?").bind(id);
}
Artist::pointer
Artist::create(Wt::Dbo::Session& session, const std::string& name, const std::string& MBID)
{
@@ -84,6 +90,24 @@ Artist::getByFilter(Wt::Dbo::Session& session, SearchFilter filter, int offset,
return std::vector<pointer>(res.begin(), res.end());
}
std::vector<Artist::pointer>
Artist::getByFilter(Wt::Dbo::Session& session, SearchFilter filter, int offset, int size, bool& moreResults)
{
auto res = getByFilter(session, filter, offset, size + 1);
if (size != -1 && res.size() == static_cast<std::size_t>(size) + 1)
{
moreResults = true;
res.pop_back();
}
else
moreResults = false;
return res;
}
std::vector<Wt::Dbo::ptr<Release> >
Artist::getReleases() const
{
+1
View File
@@ -51,6 +51,7 @@ class Artist : public Wt::Dbo::Dbo<Artist>
static pointer getNone(Wt::Dbo::Session& session); // Special entry
static std::vector<pointer> getByName(Wt::Dbo::Session& session, const std::string& name);
static std::vector<pointer> getByFilter(Wt::Dbo::Session& session, SearchFilter filter, int offset = -1, int size = -1);
static std::vector<pointer> getByFilter(Wt::Dbo::Session& session, SearchFilter filter, int offset, int size, bool& moreExpected);
static std::vector<pointer> getAll(Wt::Dbo::Session& session, int offset = -1, int size = -1);
static std::vector<pointer> getAllOrphans(Wt::Dbo::Session& session);
+16
View File
@@ -140,6 +140,22 @@ Release::getByFilter(Wt::Dbo::Session& session, SearchFilter filter, int offset,
return std::vector<pointer>(res.begin(), res.end());
}
std::vector<Release::pointer>
Release::getByFilter(Wt::Dbo::Session& session, SearchFilter filter, int offset, int size, bool& moreResults)
{
auto res = getByFilter(session, filter, offset, size + 1);
if (size != -1 && res.size() == static_cast<std::size_t>(size) + 1)
{
moreResults = true;
res.pop_back();
}
else
moreResults = false;
return res;
}
int
Release::getReleaseYear(bool original) const
{
+1
View File
@@ -49,6 +49,7 @@ class Release : public Wt::Dbo::Dbo<Release>
static std::vector<pointer> getAllOrphans(Wt::Dbo::Session& session);
static std::vector<pointer> getAll(Wt::Dbo::Session& session, int offset, int size);
static std::vector<pointer> getByFilter(Wt::Dbo::Session& session, SearchFilter filter, int offset = -1, int size = -1);
static std::vector<pointer> getByFilter(Wt::Dbo::Session& session, SearchFilter filter, int offset, int size, bool& moreExpected);
// Create
static pointer create(Wt::Dbo::Session& session, const std::string& name, const std::string& MBID = "");
+32 -1
View File
@@ -87,6 +87,20 @@ Track::getAllPaths(Wt::Dbo::Session& session)
return std::vector<boost::filesystem::path>(res.begin(), res.end());
}
std::vector<Track::pointer>
Track::getMBIDDuplicates(Wt::Dbo::Session& session)
{
Wt::Dbo::collection<pointer> res = session.query<pointer>( "SELECT track FROM track WHERE mbid in (SELECT mbid FROM track WHERE mbid <> '' GROUP BY mbid HAVING COUNT (*) > 1)").orderBy("track.release_id,track.disc_number,track.track_number,track.mbid");
return std::vector<pointer>(res.begin(), res.end());
}
std::vector<Track::pointer>
Track::getChecksumDuplicates(Wt::Dbo::Session& session)
{
Wt::Dbo::collection<pointer> res = session.query<pointer>( "SELECT track FROM track WHERE checksum in (SELECT checksum FROM track WHERE Length(checksum) > 0 GROUP BY checksum HAVING COUNT(*) > 1)").orderBy("track.release_id,track.disc_number,track.track_number,track.checksum");
return std::vector<pointer>(res.begin(), res.end());
}
std::vector< Genre::pointer >
Track::getGenres(void) const
{
@@ -128,7 +142,7 @@ Track::getStats(Wt::Dbo::Session& session, SearchFilter filter)
{
SqlQuery sqlQuery = generatePartialQuery(filter);
Wt::Dbo::Query<StatsQueryResult> query = session.query<StatsQueryResult>( "SELECT COUNT(DISTINCT t.id), SUM(t.duration) FROM track t INNER JOIN artist a ON t.artist_id = a.id INNER JOIN genre g ON g.id = t_g.genre_id INNER JOIN track_genre t_g ON t_g.track_id = t.id INNER JOIN release r ON r.id = t.release_id " + sqlQuery.where().get());
Wt::Dbo::Query<StatsQueryResult> query = session.query<StatsQueryResult>( "SELECT COUNT(\"id\"), SUM(\"dur\") FROM (SELECT t.id as \"id\", t.duration as \"dur\" FROM track t INNER JOIN artist a ON t.artist_id = a.id INNER JOIN genre g ON g.id = t_g.genre_id INNER JOIN track_genre t_g ON t_g.track_id = t.id INNER JOIN release r ON r.id = t.release_id " + sqlQuery.where().get() + " GROUP BY t.id)");
for (const std::string& bindArg : sqlQuery.where().getBindArgs())
query.bind(bindArg);
@@ -145,6 +159,22 @@ Track::getByFilter(Wt::Dbo::Session& session, SearchFilter filter, int offset, i
return std::vector<pointer>(res.begin(), res.end());
}
std::vector<Track::pointer>
Track::getByFilter(Wt::Dbo::Session& session, SearchFilter filter, int offset, int size, bool& moreResults)
{
auto res = getByFilter(session, filter, offset, size + 1);
if (size != -1 && res.size() == static_cast<std::size_t>(size) + 1)
{
moreResults = true;
res.pop_back();
}
else
moreResults = false;
return res;
}
void
Track::updateUIQueryModel(Wt::Dbo::Session& session, Wt::Dbo::QueryModel< UIQueryResult >& model, SearchFilter filter, const std::vector<Wt::WString>& columnNames)
{
@@ -167,6 +197,7 @@ Track::updateUIQueryModel(Wt::Dbo::Session& session, Wt::Dbo::QueryModel< UIQuer
}
Genre::Genre()
{
}
+4
View File
@@ -53,6 +53,7 @@ class Genre
static pointer getNone(Wt::Dbo::Session& session);
static std::vector<pointer> getByFilter(Wt::Dbo::Session& session, SearchFilter filter, int offset = -1, int size = -1);
static Wt::Dbo::collection<Genre::pointer> getAll(Wt::Dbo::Session& session);
// MVC models for the user interface
// Genre ID, name, track count
typedef boost::tuple<id_type, std::string, int> UIQueryResult;
@@ -104,8 +105,11 @@ class Track
static pointer getById(Wt::Dbo::Session& session, id_type id);
static pointer getByMBID(Wt::Dbo::Session& session, const std::string& MBID);
static std::vector<pointer> getByFilter(Wt::Dbo::Session& session, SearchFilter filter, int offset = -1, int size = -1);
static std::vector<pointer> getByFilter(Wt::Dbo::Session& session, SearchFilter filter, int offset, int size, bool &moreResults);
static Wt::Dbo::collection< pointer > getAll(Wt::Dbo::Session& session);
static std::vector<boost::filesystem::path> getAllPaths(Wt::Dbo::Session& session);
static std::vector<pointer> getMBIDDuplicates(Wt::Dbo::Session& session);
static std::vector<pointer> getChecksumDuplicates(Wt::Dbo::Session& session);
// Utility fonctions
// MVC models for the user interface
+7 -1
View File
@@ -171,7 +171,7 @@ AvFormat::parse(const boost::filesystem::path& p, Items& items)
{
// TODO use splitStrings
std::list<std::string> genres;
if (readList(it->second, ";,", genres))
if (readList(it->second, ";,\\", genres))
items.insert( std::make_pair(MetaData::Type::Genres, genres));
}
@@ -185,6 +185,12 @@ AvFormat::parse(const boost::filesystem::path& p, Items& items)
{
items.insert( std::make_pair(MetaData::Type::MusicBrainzAlbumID, stringTrim( stringToUTF8(it->second)) ));
}
else if (boost::iequals(it->first, "MusicBrainz Release Track Id")
|| boost::iequals(it->first, "MUSICBRAINZ_RELEASETRACKID")
|| boost::iequals(it->first, "MUSICBRAINZ_TRACKID"))
{
items.insert( std::make_pair(MetaData::Type::MusicBrainzTrackID, stringTrim( stringToUTF8(it->second)) ));
}
}
return true;
+1
View File
@@ -47,6 +47,7 @@ namespace MetaData
SubtitleStreams, // vector<SubtitleStream>
MusicBrainzArtistID, // string
MusicBrainzAlbumID, // string
MusicBrainzTrackID, // string
};
// Used by Streams
+81 -78
View File
@@ -28,8 +28,8 @@
#include <Wt/Auth/Identity>
#include "config/config.h"
#include "logger/Logger.hpp"
#include "utils/Utils.hpp"
#include "settings/Settings.hpp"
#include "settings/SettingsFirstConnectionFormView.hpp"
@@ -59,10 +59,8 @@ bool agentIsMobile()
|| env.userAgent().find("Tablet") != std::string::npos // Workaround for firefox
);
}
}
namespace UserInterface {
Wt::WApplication*
@@ -90,9 +88,9 @@ LmsApplication::instance()
LmsApplication::LmsApplication(const Wt::WEnvironment& env, Wt::Dbo::SqlConnectionPool& connectionPool)
: Wt::WApplication(env),
_db(connectionPool),
_coverResource(nullptr)
_imageResource(nullptr),
_transcodeResource(nullptr)
{
Wt::WBootstrapTheme *bootstrapTheme = new Wt::WBootstrapTheme(this);
bootstrapTheme->setVersion(Wt::WBootstrapTheme::Version3);
bootstrapTheme->setResponsive(true);
@@ -120,7 +118,6 @@ LmsApplication::LmsApplication(const Wt::WEnvironment& env, Wt::Dbo::SqlConnecti
createFirstConnectionUI();
else
createLmsUI();
}
Database::Handler& DbHandler()
@@ -142,9 +139,9 @@ Database::User::pointer CurrentUser()
return DbHandler().getCurrentUser();
}
CoverResource* SessionCoverResource()
ImageResource* SessionImageResource()
{
return LmsApplication::instance()->getCoverResource();
return LmsApplication::instance()->getImageResource();
}
TranscodeResource* SessionTranscodeResource()
@@ -164,7 +161,7 @@ LmsApplication::createFirstConnectionUI()
void
LmsApplication::createLmsUI()
{
_coverResource = new CoverResource(_db, root());
_imageResource = new ImageResource(_db, root());
_transcodeResource = new TranscodeResource(_db, root());
DbHandler().getLogin().changed().connect(this, &LmsApplication::handleAuthEvent);
@@ -179,84 +176,90 @@ LmsApplication::createLmsUI()
root()->addWidget(authWidget);
}
void
LmsApplication::handleAuthEvent(void)
{
if (DbHandler().getLogin().loggedIn())
{
LMS_LOG(UI, INFO) << "User '" << CurrentAuthUser().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);
setConfirmCloseMessage("Closing LMS. Are you sure?");
// Create a Vertical layout: top is the nav bar, bottom is the contents
Wt::WVBoxLayout *layout = new Wt::WVBoxLayout(this->root());
// Create a navigation bar with a link to a web page.
Wt::WNavigationBar *navigation = new Wt::WNavigationBar();
navigation->setTitle("LMS", "https://github.com/epoupon/lms");
navigation->setResponsive(true);
navigation->addStyleClass("main-nav");
Wt::WStackedWidget *contentsStack = new Wt::WStackedWidget();
contentsStack->setOverflow(Wt::WContainerWidget::OverflowAuto);
contentsStack->addStyleClass("contents");
Wt::WMenu *menu = new Wt::WMenu(contentsStack);
navigation->addMenu(menu, Wt::AlignRight);
LineEdit *searchEdit = new LineEdit(500);
navigation->bindWidget("search", searchEdit);
searchEdit->setEmptyText("Search...");
searchEdit->addStyleClass("navbar-form navbar-nav");
searchEdit->setWidth(150);
// TODO add a span with a search icon
Audio *audio;
if (agentIsMobile())
audio = new Mobile::Audio();
else
audio = new Desktop::Audio();
menu->addItem("Audio", audio);
#if defined HAVE_VIDEO
menu->addItem("Video", new VideoWidget());
#endif
menu->addItem("Settings", new Settings::Settings());
Wt::WPopupMenu *popup = new Wt::WPopupMenu();
popup->addItem("Logout");
Wt::WMenuItem *item = new Wt::WMenuItem( CurrentAuthUser().identity(Wt::Auth::Identity::LoginName) );
item->setMenu(popup);
menu->addItem(item);
popup->itemSelected().connect(std::bind([=] (Wt::WMenuItem* item)
{
if (item && item->text() == "Logout")
DbHandler().getLogin().logout();
}, std::placeholders::_1));
searchEdit->timedChanged().connect(std::bind([=] ()
{
// TODO: check which view is activated and search into it
audio->search(searchEdit->text().toUTF8());
}));
layout->addWidget(navigation);
layout->addWidget(contentsStack, 1);
layout->setContentsMargins(0, 0, 0, 0);
}
else
if (!DbHandler().getLogin().loggedIn())
{
LMS_LOG(UI, INFO) << "User logged out, session = " << Wt::WApplication::instance()->sessionId();
quit("");
redirect("/");
return;
}
LMS_LOG(UI, INFO) << "User '" << CurrentAuthUser().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);
setConfirmCloseMessage("Closing LMS. Are you sure?");
// Create a Vertical layout: top is the nav bar, bottom is the contents
Wt::WVBoxLayout *layout = new Wt::WVBoxLayout(this->root());
Wt::WNavigationBar *navigation = new Wt::WNavigationBar();
navigation->setTitle("LMS", "https://github.com/epoupon/lms");
navigation->setResponsive(true);
navigation->addStyleClass("main-nav");
Wt::WStackedWidget *contentsStack = new Wt::WStackedWidget();
contentsStack->setOverflow(Wt::WContainerWidget::OverflowAuto);
contentsStack->addStyleClass("contents");
Wt::WMenu *menu = new Wt::WMenu(contentsStack);
navigation->addMenu(menu, Wt::AlignRight);
LineEdit *searchEdit = new LineEdit(500);
navigation->bindWidget("search", searchEdit);
searchEdit->setEmptyText("Search...");
searchEdit->addStyleClass("navbar-form navbar-nav");
searchEdit->setWidth(150);
// TODO add a span with a search icon
menu->setInternalPathEnabled();
menu->setInternalBasePath("/");
Audio *audio;
if (agentIsMobile())
audio = new Mobile::Audio();
else
audio = new Desktop::Audio();
menu->addItem("Audio", audio)->setPathComponent("audio");;
#if defined HAVE_VIDEO
menu->addItem("Video", new VideoWidget())->setPathComponent("video");
#endif
menu->addItem("Settings", new Settings::Settings())->setPathComponent("settings");
Wt::WPopupMenu *popup = new Wt::WPopupMenu();
popup->addItem("Logout");
Wt::WMenuItem *item = new Wt::WMenuItem( CurrentAuthUser().identity(Wt::Auth::Identity::LoginName) );
item->setMenu(popup);
menu->addItem(item);
popup->itemSelected().connect(std::bind([=] (Wt::WMenuItem* item)
{
if (item && item->text() == "Logout")
DbHandler().getLogin().logout();
}, std::placeholders::_1));
searchEdit->timedChanged().connect(std::bind([=] ()
{
// TODO: check which view is activated and search into it
audio->search(searchEdit->text().toUTF8());
}));
layout->addWidget(navigation);
layout->addWidget(contentsStack, 1);
layout->setContentsMargins(0, 0, 0, 0);
if (agentIsMobile())
setInternalPath("/audio/search/preview", true);
else
setInternalPath("/audio");
}
} // namespace UserInterface
+6 -4
View File
@@ -26,7 +26,7 @@
#include <Wt/Dbo/SqlConnectionPool>
#include "database/DatabaseHandler.hpp"
#include "resource/CoverResource.hpp"
#include "resource/ImageResource.hpp"
#include "resource/TranscodeResource.hpp"
namespace UserInterface {
@@ -40,7 +40,7 @@ class LmsApplication : public Wt::WApplication
LmsApplication(const Wt::WEnvironment& env, Wt::Dbo::SqlConnectionPool& connectionPool);
// Session application data
CoverResource* getCoverResource() { return _coverResource; }
ImageResource* getImageResource() { return _imageResource; }
TranscodeResource* getTranscodeResource() { return _transcodeResource; }
Database::Handler& getDbHandler() { return _db;}
@@ -51,18 +51,20 @@ class LmsApplication : public Wt::WApplication
void createLmsUI();
Database::Handler _db;
CoverResource* _coverResource;
ImageResource* _imageResource;
TranscodeResource* _transcodeResource;
};
// Helpers to get session data
#define LmsApp LmsApplication::instance()
Database::Handler& DbHandler();
Wt::Dbo::Session& DboSession();
const Wt::Auth::User& CurrentAuthUser();
Database::User::pointer CurrentUser();
CoverResource *SessionCoverResource();
ImageResource *SessionImageResource();
TranscodeResource *SessionTranscodeResource();
} // namespace UserInterface
+51 -32
View File
@@ -42,7 +42,6 @@ AudioPlayer::getBestEncoding() const
return Av::Encoding::MP3;
}
bool
AudioPlayer::loadTrack(Database::Track::id_type trackId)
{
@@ -77,7 +76,7 @@ AudioPlayer::loadTrack(Database::Track::id_type trackId)
bindString("track", Wt::WString::fromUTF8(track->getName()));
bindString("artist", Wt::WString::fromUTF8(track->getArtist()->getName()));
_cover->setImageLink(SessionCoverResource()->getTrackUrl(trackId, 64));
_cover->setImageLink(SessionImageResource()->getTrackUrl(trackId, 64));
std::string durationFormat = track->getDuration().total_seconds() < 3600 ? "%M:%S" : "%H:%M:%S";
@@ -121,7 +120,7 @@ AudioPlayer::loadTrack(Database::Track::id_type trackId)
return true;
}
AudioPlayer::AudioPlayer(Wt::WContainerWidget *parent)
AudioPlayer::AudioPlayer(ControlFlags controls, Wt::WContainerWidget *parent)
: Wt::WTemplate(parent)
{
setTemplateText(Wt::WString::tr("wa-audio-player"));
@@ -140,7 +139,7 @@ AudioPlayer::AudioPlayer(Wt::WContainerWidget *parent)
_cover = new Wt::WImage();
bindWidget("cover", _cover);
_cover->setImageLink(SessionCoverResource()->getUnknownTrackUrl(64));
_cover->setImageLink(SessionImageResource()->getUnknownTrackUrl(64));
InputRange *seekbar = new InputRange();
seekbar->addStyleClass("mediaplayer-seekbar");
@@ -154,42 +153,62 @@ AudioPlayer::AudioPlayer(Wt::WContainerWidget *parent)
volumeSlider->setAttributeValue("orient", "vertical"); // firefox
bindWidget("volume", volumeSlider);
Wt::WText *repeatBtn = new Wt::WText("<i class=\"fa fa-repeat fa-2x\"></i>", Wt::XHTMLText);
repeatBtn->addStyleClass("mediaplayer-btn");
bindWidget("repeat", repeatBtn);
repeatBtn->clicked().connect(std::bind([=] ()
if (controls & ControlRepeat)
{
if (repeatBtn->hasStyleClass("mediaplayer-btn-active"))
setCondition("if-has-repeat", true);
Wt::WText *repeatBtn = new Wt::WText("<i class=\"fa fa-repeat fa-2x\"></i>", Wt::XHTMLText);
repeatBtn->addStyleClass("mediaplayer-btn hidden-xs");
bindWidget("repeat", repeatBtn);
repeatBtn->clicked().connect(std::bind([=] ()
{
repeatBtn->removeStyleClass("mediaplayer-btn-active");
_loop.emit(false);
}
else
{
repeatBtn->addStyleClass("mediaplayer-btn-active");
_loop.emit(true);
}
}));
if (repeatBtn->hasStyleClass("mediaplayer-btn-active"))
{
repeatBtn->removeStyleClass("mediaplayer-btn-active");
_loop.emit(false);
}
else
{
repeatBtn->addStyleClass("mediaplayer-btn-active");
_loop.emit(true);
}
}));
}
Wt::WText *shuffleBtn = new Wt::WText("<i class=\"fa fa-random fa-2x\"></i>", Wt::XHTMLText);
shuffleBtn->addStyleClass("mediaplayer-btn");
bindWidget("shuffle", shuffleBtn);
shuffleBtn->clicked().connect(std::bind([=] ()
if (controls & ControlPlayqueue)
{
if (shuffleBtn->hasStyleClass("mediaplayer-btn-active"))
setCondition("if-has-playqueue", true);
Wt::WText *playQueueBtn = new Wt::WText("<i class=\"fa fa-list-ul fa-2x\"></i>", Wt::XHTMLText);
bindWidget("playqueue", playQueueBtn);
playQueueBtn->addStyleClass("mediaplayer-btn");
playQueueBtn->clicked().connect(std::bind([=] ()
{
shuffleBtn->removeStyleClass("mediaplayer-btn-active");
_shuffle.emit(false);
}
else
_playQueue.emit();
}));
}
if (controls & ControlShuffle)
{
setCondition("if-has-shuffle", true);
Wt::WText *shuffleBtn = new Wt::WText("<i class=\"fa fa-random fa-2x\"></i>", Wt::XHTMLText);
shuffleBtn->addStyleClass("mediaplayer-btn hidden-xs");
bindWidget("shuffle", shuffleBtn);
shuffleBtn->clicked().connect(std::bind([=] ()
{
shuffleBtn->addStyleClass("mediaplayer-btn-active");
_shuffle.emit(true);
}
}));
if (shuffleBtn->hasStyleClass("mediaplayer-btn-active"))
{
shuffleBtn->removeStyleClass("mediaplayer-btn-active");
_shuffle.emit(false);
}
else
{
shuffleBtn->addStyleClass("mediaplayer-btn-active");
_shuffle.emit(true);
}
}));
}
Wt::WText *prevBtn = new Wt::WText("<i class=\"fa fa-step-backward fa-2x\"></i>", Wt::XHTMLText);
prevBtn->addStyleClass("mediaplayer-btn hidden-xs");
prevBtn->addStyleClass("mediaplayer-btn");
bindWidget("prev", prevBtn);
prevBtn->clicked().connect(std::bind([=] ()
{
+14 -1
View File
@@ -32,7 +32,15 @@ class AudioPlayer : public Wt::WTemplate
{
public:
AudioPlayer(Wt::WContainerWidget *parent = 0);
enum ControlFlags
{
ControlNone = 0,
ControlShuffle = 1 << 0,
ControlRepeat = 1 << 1,
ControlPlayqueue = 1 << 2,
};
AudioPlayer(ControlFlags controls, Wt::WContainerWidget *parent = 0);
Av::Encoding getBestEncoding() const;
bool loadTrack(Database::Track::id_type trackId);
@@ -43,6 +51,7 @@ class AudioPlayer : public Wt::WTemplate
Wt::Signal<void>& playPrevious() {return _playPrevious;}
Wt::Signal<bool>& shuffle() {return _shuffle;}
Wt::Signal<bool>& loop() {return _loop;}
Wt::Signal<void>& showPlayQueue() {return _playQueue;}
private:
@@ -52,6 +61,7 @@ class AudioPlayer : public Wt::WTemplate
Wt::Signal<void> _playPrevious;
Wt::Signal<bool> _shuffle;
Wt::Signal<bool> _loop;
Wt::Signal<void> _playQueue;
Wt::WAudio* _audio;
Wt::WText* _trackCurTime;
@@ -60,4 +70,7 @@ class AudioPlayer : public Wt::WTemplate
Wt::WImage* _cover;
};
constexpr AudioPlayer::ControlFlags operator|(AudioPlayer::ControlFlags f1, AudioPlayer::ControlFlags f2) { return AudioPlayer::ControlFlags(int(f1)|int(f2)); }
} // namespace UserInterface
+25 -6
View File
@@ -149,23 +149,41 @@ _playQueue(nullptr)
playlistBtn->setMenu(popupMain);
}
Wt::WPushButton *upBtn = new Wt::WPushButton("UP");
Wt::WPushButton *upBtn = new Wt::WPushButton("<i class =\"fa fa-arrow-up fa-lg\"></i>", Wt::XHTMLText);
upBtn->setStyleClass("btn-sm");
playlistControls->addWidget(upBtn);
Wt::WPushButton *downBtn = new Wt::WPushButton("DO");
Wt::WPushButton *downBtn = new Wt::WPushButton("<i class =\"fa fa-arrow-down fa-lg\"></i>", Wt::XHTMLText);
downBtn->setStyleClass("btn-sm");
playlistControls->addWidget(downBtn);
Wt::WPushButton *delBtn = new Wt::WPushButton("DEL");
Wt::WPushButton *delBtn = new Wt::WPushButton("<i class =\"fa fa-remove fa-lg\"></i>", Wt::XHTMLText);
delBtn->setStyleClass("btn-sm btn-warning");
playlistControls->addWidget(delBtn);
Wt::WPushButton *clearBtn = new Wt::WPushButton("CLR");
Wt::WPushButton *clearBtn = new Wt::WPushButton("<i class =\"fa fa-trash fa-lg\"></i>", Wt::XHTMLText);
clearBtn->setStyleClass("btn-sm btn-danger");
playlistControls->addWidget(clearBtn);
delBtn->clicked().connect(_playQueue, &PlayQueue::delSelected);
upBtn->clicked().connect(_playQueue, &PlayQueue::moveSelectedUp);
downBtn->clicked().connect(_playQueue, &PlayQueue::moveSelectedDown);
clearBtn->clicked().connect(_playQueue, &PlayQueue::delAll);
clearBtn->clicked().connect(std::bind([=]
{
Wt::WMessageBox *messageBox = new Wt::WMessageBox
("Clear playqueue",
Wt::WString( "Are you sure?"),
Wt::Question, Wt::Yes | Wt::No);
messageBox->setModal(true);
messageBox->buttonClicked().connect(std::bind([=] ()
{
if (messageBox->buttonResult() == Wt::Yes)
_playQueue->delAll();
delete messageBox;
}));
messageBox->show();
}));
playQueueLayout->addLayout(playlistControls);
@@ -180,7 +198,7 @@ _playQueue(nullptr)
_playQueue->select(CurrentUser()->getCurPlayingTrackPos());
}
_mediaPlayer = new AudioPlayer();
_mediaPlayer = new AudioPlayer(AudioPlayer::ControlShuffle | AudioPlayer::ControlRepeat);
mainLayout->addWidget(_mediaPlayer, 2, 0, 1, 4);
mainLayout->setRowStretch(1, 1);
@@ -432,6 +450,7 @@ Audio::playlistRefreshMenus()
}
}
void
Audio::search(std::string searchText)
{
+1 -1
View File
@@ -387,7 +387,7 @@ PlayQueue::addTracks(const std::vector<Database::Track::id_type>& trackIds)
_model->setData(dataRow, COLUMN_ID_TRACK_ID, track.id(), Wt::UserRole);
std::string coverUrl;
coverUrl = LmsApplication::instance()->getCoverResource()->getTrackUrl(track.id(), 64);
coverUrl = SessionImageResource()->getTrackUrl(track.id(), 64);
_model->setData(dataRow, COLUMN_ID_COVER, coverUrl, Wt::DecorationRole);
_model->setData(dataRow, COLUMN_ID_COVER, std::string("playqueue-cover"), Wt::StyleClassRole);
-2
View File
@@ -25,8 +25,6 @@
#include "database/Types.hpp"
#include "resource/CoverResource.hpp"
namespace UserInterface {
namespace Desktop {
+3 -3
View File
@@ -55,7 +55,7 @@ TableFilterGenre::TableFilterGenre(Wt::WContainerWidget* parent)
this->setLayoutSizeAware(true);
_queryModel.setBatchSize(100);
_queryModel.setBatchSize(150);
// If an item is double clicked, select and emit signal
this->doubleClicked().connect( std::bind([=] (Wt::WModelIndex idx, Wt::WMouseEvent evt)
@@ -134,7 +134,7 @@ TableFilterArtist::TableFilterArtist(Wt::WContainerWidget* parent)
this->setLayoutSizeAware(true);
_queryModel.setBatchSize(100);
_queryModel.setBatchSize(150);
// If an item is double clicked, select and emit signal
this->doubleClicked().connect( std::bind([=] (Wt::WModelIndex idx, Wt::WMouseEvent evt)
@@ -218,7 +218,7 @@ TableFilterRelease::TableFilterRelease(Wt::WContainerWidget* parent)
this->setLayoutSizeAware(true);
_queryModel.setBatchSize(100);
_queryModel.setBatchSize(150);
// If an item is double clicked, select and emit signal
this->doubleClicked().connect( std::bind([=] (Wt::WModelIndex idx, Wt::WMouseEvent evt)
+1 -1
View File
@@ -70,7 +70,7 @@ TrackView::TrackView(Wt::WContainerWidget* parent)
Track::updateUIQueryModel(DboSession(), _queryModel, filter, columnNames);
_queryModel.setBatchSize(300);
_queryModel.setBatchSize(500);
this->setSortingEnabled(true);
this->setSelectionMode(Wt::ExtendedSelection);
+39 -55
View File
@@ -29,91 +29,75 @@
namespace UserInterface {
namespace Mobile {
using namespace Database;
ArtistSearch::ArtistSearch(Wt::WContainerWidget *parent)
: Wt::WContainerWidget(parent),
_resCount(0)
ArtistSearch::ArtistSearch(Wt::WString title, Wt::WContainerWidget *parent)
: Wt::WContainerWidget(parent)
{
Wt::WTemplate *title = new Wt::WTemplate(this);
title->setTemplateText(Wt::WString::tr("mobile-search-title"));
Wt::WTemplate* t = new Wt::WTemplate(this);
t->setTemplateText(Wt::WString::tr("wa-artist-search"));
title->bindWidget("text", new Wt::WText("Artists", Wt::PlainText));
Wt::WTemplate *titleTemplate = new Wt::WTemplate(this);
titleTemplate->setTemplateText(Wt::WString::tr("mobile-search-title"));
titleTemplate->bindString("text", title);
t->bindWidget("title", titleTemplate);
Wt::WTemplate* artistWrapper = new Wt::WTemplate(this);
artistWrapper->setTemplateText(Wt::WString::tr("wa-artist-wrapper"));
_contents = new Wt::WContainerWidget();
artistWrapper->bindWidget("contents", _contents );
t->bindWidget("contents", _contents );
_showMore = new Wt::WTemplate();
_showMore->setTemplateText(Wt::WString::tr("mobile-search-more"));
_showMore->bindString("text", "Tap to show more results...");
_showMore->hide();
_showMore->clicked().connect(std::bind([=] {
_sigShowMore.emit();
}));
t->bindWidget("show-more", _showMore);
}
void
ArtistSearch::clear()
{
while (count() > 1)
removeWidget(this->widget(1));
_resCount = 0;
_contents->clear();
_showMore->hide();
}
void
ArtistSearch::search(Database::SearchFilter filter, size_t nb)
{
_filter = filter;
clear();
addResults(filter, nb);
addResults(nb);
}
void
ArtistSearch::addResults(Database::SearchFilter filter, std::size_t nb)
ArtistSearch::addResults(std::size_t nb)
{
using namespace Database;
Wt::Dbo::Transaction transaction(DboSession());
std::vector<Artist::pointer> artists = Artist::getByFilter(DboSession(), filter, _resCount, nb + 1);
bool expectMoreResults;
if (artists.size() == nb + 1)
{
expectMoreResults = true;
artists.pop_back();
}
else
expectMoreResults = false;
bool moreResults;
std::vector<Artist::pointer> artists = Artist::getByFilter(DboSession(), _filter, _contents->count(), nb, moreResults);
for (Artist::pointer artist : artists)
{
Wt::WTemplate* res = new Wt::WTemplate(this);
res->setTemplateText(Wt::WString::tr("wa-artist-res"));
Wt::WTemplate* res = new Wt::WTemplate(_contents);
res->setTemplateText(Wt::WString::tr("wa-artist-search-res"));
Wt::WImage *artistImg = new Wt::WImage();
Wt::WAnchor *coverAnchor = new Wt::WAnchor(Wt::WLink(Wt:: WLink::InternalPath, "/audio/artist/" + std::to_string(artist.id())));
Wt::WImage *artistImg = new Wt::WImage(coverAnchor);
artistImg->setImageLink( SessionImageResource()->getArtistUrl(artist.id(), 512));
artistImg->setStyleClass("center-block"); // TODO move in css?
artistImg->setStyleClass("release_res_shadow release_img-responsive"); // TODO move in css?
res->bindWidget("gif", artistImg);
Wt::WText *text = new Wt::WText(Wt::WString::fromUTF8(artist->getName()), Wt::PlainText);
res->bindWidget("name", text);
res->clicked().connect(std::bind([=] {
_sigArtistSelected(artist.id());
}));
}
_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);
}));
res->bindWidget("gif", coverAnchor);
res->bindString("name", Wt::WString::fromUTF8(artist->getName(), Wt::PlainText));
}
if (moreResults)
_showMore->show();
else
_showMore->hide();
}
} // namespace Mobile
+10 -8
View File
@@ -23,6 +23,7 @@
#include <Wt/WContainerWidget>
#include "database/Types.hpp"
#include "database/SearchFilter.hpp"
namespace UserInterface {
namespace Mobile {
@@ -31,24 +32,25 @@ class ArtistSearch : public Wt::WContainerWidget
{
public:
ArtistSearch(Wt::WContainerWidget *parent = 0);
ArtistSearch(Wt::WString title, Wt::WContainerWidget *parent = 0);
void search(Database::SearchFilter filter, std::size_t nb);
void addResults(std::size_t nb);
// Slots
Wt::Signal<Database::Artist::id_type>& artistSelected() { return _sigArtistSelected;}
Wt::Signal<void>& moreArtistsSelected() { return _sigMoreArtistsSelected;}
Wt::Signal<void>& showMore() { return _sigShowMore;}
private:
Wt::Signal<Database::Artist::id_type> _sigArtistSelected;
Wt::Signal<void> _sigMoreArtistsSelected;
Wt::Signal<void> _sigShowMore;
void clear(void);
void addResults(Database::SearchFilter filter, size_t nb);
Wt::WContainerWidget* _contents;
std::size_t _resCount;
Wt::WTemplate* _showMore;
Database::SearchFilter _filter;
Wt::WContainerWidget* _contents;
std::size_t _count;
};
} // namespace Mobile
+60
View File
@@ -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 <http://www.gnu.org/licenses/>.
*/
#include <Wt/WApplication>
#include <Wt/WText>
#include "logger/Logger.hpp"
#include "SearchUtils.hpp"
#include "ArtistSearch.hpp"
#include "ArtistSearchView.hpp"
namespace UserInterface {
namespace Mobile {
#define SEARCH_NB_ITEMS 20
using namespace Database;
ArtistSearchView::ArtistSearchView(Wt::WContainerWidget* parent)
{
ArtistSearch* artistSearch = new ArtistSearch("Artists", this);
artistSearch->showMore().connect(std::bind([=] {
artistSearch->addResults(SEARCH_NB_ITEMS);
}));
wApp->internalPathChanged().connect(std::bind([=] (std::string path)
{
const std::string pathPrefix = "/audio/search/artist";
if (!wApp->internalPathMatches(pathPrefix))
return;
std::vector<std::string> keywords = searchPathToSearchKeywords(path.substr(pathPrefix.length()));
artistSearch->search(SearchFilter::ByNameAnd(SearchFilter::Field::Artist, keywords), SEARCH_NB_ITEMS);
}, std::placeholders::_1));
}
} // namespace Mobile
} // namespace UserInterface
+37
View File
@@ -0,0 +1,37 @@
/*
* 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 <http://www.gnu.org/licenses/>.
*/
#pragma once
#include <Wt/WContainerWidget>
namespace UserInterface {
namespace Mobile {
class ArtistSearchView : public Wt::WContainerWidget
{
public:
ArtistSearchView(Wt::WContainerWidget* parent = 0);
};
} // namespace Mobile
} // namespace UserInterface
+66
View File
@@ -0,0 +1,66 @@
/*
* 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 <http://www.gnu.org/licenses/>.
*/
#include "LmsApplication.hpp"
#include "utils/Utils.hpp"
#include "ReleaseSearch.hpp"
#include "ArtistView.hpp"
namespace UserInterface {
namespace Mobile {
using namespace Database;
ArtistView::ArtistView(Wt::WContainerWidget *parent)
: Wt::WContainerWidget(parent)
{
ReleaseSearch *releases = new ReleaseSearch(this);
wApp->internalPathChanged().connect(std::bind([=] (std::string path)
{
const std::string pathPrefix = "/audio/artist/";
if (!wApp->internalPathMatches(pathPrefix))
return;
std::string strId = path.substr(pathPrefix.length());
Artist::id_type id;
if (readAs(strId, id))
{
Wt::Dbo::Transaction transaction(DboSession());
auto artist = Artist::getById(DboSession(), id);
releases->search(SearchFilter::ById(SearchFilter::Field::Artist, id), 20, artist ? Wt::WString::fromUTF8(artist->getName()) : "Unknown artist");
}
}, std::placeholders::_1));
}
Wt::WLink
ArtistView::getLink(Database::Artist::id_type id)
{
return Wt::WLink(Wt::WLink::InternalPath, "/audio/artist/" + std::to_string(id));
}
} //namespace Mobile
} //namespace UserInterface
+43
View File
@@ -0,0 +1,43 @@
/*
* 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 <http://www.gnu.org/licenses/>.
*/
#pragma once
#include <Wt/WContainerWidget>
#include <Wt/WLink>
#include "database/Types.hpp"
namespace UserInterface {
namespace Mobile {
class ArtistView : public Wt::WContainerWidget
{
public:
static Wt::WLink getLink(Database::Artist::id_type id);
ArtistView(Wt::WContainerWidget *parent = 0);
};
} //namespace Mobile
} //namespace UserInterface
+79 -79
View File
@@ -17,8 +17,9 @@
* along with LMS. If not, see <http://www.gnu.org/licenses/>.
*/
#include <map>
#include <Wt/WContainerWidget>
#include <Wt/WStackedWidget>
#include <Wt/WTemplate>
#include <Wt/WText>
@@ -28,47 +29,41 @@
#include "audio/AudioPlayer.hpp"
#include "LmsApplication.hpp"
#include "PreviewSearchView.hpp"
#include "ArtistSearchView.hpp"
#include "ReleaseSearchView.hpp"
#include "TrackSearchView.hpp"
#include "ArtistView.hpp"
#include "ReleaseView.hpp"
#include "MobilePlayQueue.hpp"
#include "MobileAudio.hpp"
#define SEARCH_NB_ITEMS 4
namespace UserInterface {
namespace Mobile {
using namespace Database;
static void playTrack(AudioPlayer *audioPlayer, Database::Track::id_type trackId)
enum WidgetIdx
{
LMS_LOG(UI, DEBUG) << "Playing track id " << trackId;
WidgetIdxSearchPreview = 0,
WidgetIdxSearchArtist = 1,
WidgetIdxSearchRelease = 2,
WidgetIdxSearchTrack = 3,
WidgetIdxArtist = 4,
WidgetIdxRelease = 5,
WidgetIdxPlayQueue = 6,
};
Wt::Dbo::Transaction transaction(DboSession());
Track::pointer track = Track::getById(DboSession(), trackId);
if (track)
audioPlayer->loadTrack(track.id());
}
using namespace Database;
void
Audio::search(std::string text)
{
// When a new search is done, output some results from:
// Artist
// Release
// Song
auto keywords = splitString(text, " ");;
_releaseSearch->search(SearchFilter::ByNameAnd(SearchFilter::Field::Release, keywords), SEARCH_NB_ITEMS);
_artistSearch->search(SearchFilter::ByNameAnd(SearchFilter::Field::Artist, keywords), SEARCH_NB_ITEMS);
_trackSearch->search(SearchFilter::ByNameAnd(SearchFilter::Field::Track, keywords), SEARCH_NB_ITEMS);
_artistSearch->show();
_releaseSearch->show();
_trackSearch->show();
_trackReleaseView->hide();
wApp->setInternalPath("/audio/search/preview/" + stringToUTF8(text), true);
}
Audio::Audio(Wt::WContainerWidget *parent)
: UserInterface::Audio(parent)
{
@@ -76,79 +71,84 @@ Audio::Audio(Wt::WContainerWidget *parent)
this->setStyleClass("container-fluid");
this->setPadding(60, Wt::Bottom);
_artistSearch = new ArtistSearch(this);
_releaseSearch = new ReleaseSearch(this);
_trackSearch = new TrackSearch(this);
Wt::WStackedWidget *stack = new Wt::WStackedWidget(this);
_trackReleaseView = new TrackReleaseView(this);
_trackReleaseView->hide();
PlayQueue *playQueue = new PlayQueue();
// Same order as WidgetIdxXXX
stack->addWidget(new PreviewSearchView(_playQueueEvents));
stack->addWidget(new ArtistSearchView());
stack->addWidget(new ReleaseSearchView());
stack->addWidget(new TrackSearchView(_playQueueEvents));
stack->addWidget(new ArtistView());
stack->addWidget(new ReleaseView(_playQueueEvents));
stack->addWidget(playQueue);
wApp->internalPathChanged().connect(std::bind([=] (std::string path)
{
// order is important: most specific path first
static std::map<std::string, int> indexes =
{
{ "/audio/search/preview", WidgetIdxSearchPreview},
{ "/audio/search/artist", WidgetIdxSearchArtist},
{ "/audio/search/release", WidgetIdxSearchRelease},
{ "/audio/search/track", WidgetIdxSearchTrack},
{ "/audio/artist", WidgetIdxArtist},
{ "/audio/release", WidgetIdxRelease},
{ "/audio/playqueue", WidgetIdxPlayQueue},
};
for (auto index : indexes)
{
if (wApp->internalPathMatches(index.first))
stack->setCurrentIndex(index.second);
}
}, std::placeholders::_1));
Wt::WTemplate* footer = new Wt::WTemplate(this);
footer->setTemplateText(Wt::WString::tr("mobile-audio-footer"));
AudioPlayer* audioPlayer = new AudioPlayer();
AudioPlayer* audioPlayer = new AudioPlayer(AudioPlayer::ControlPlayqueue);
footer->bindWidget("player", audioPlayer);
_artistSearch->moreArtistsSelected().connect(std::bind([=]
audioPlayer->showPlayQueue().connect(std::bind([=]
{
_releaseSearch->hide();
_trackSearch->hide();
_artistSearch->show();
_trackReleaseView->hide();
wApp->setInternalPath("/audio/playqueue", true);
}));
_artistSearch->artistSelected().connect(std::bind([=] (Artist::id_type artistId)
// Connect the audio player events to the playqueue
playQueue->playTrack().connect(std::bind([=] (Database::Track::id_type id)
{
_artistSearch->hide();
_trackSearch->hide();
_releaseSearch->show();
_trackReleaseView->hide();
_releaseSearch->search(SearchFilter::ById(SearchFilter::Field::Artist, artistId), 20);
audioPlayer->loadTrack(id);
}, std::placeholders::_1));
_releaseSearch->moreReleasesSelected().connect(std::bind([=]
audioPlayer->playNext().connect(std::bind([=]
{
_artistSearch->hide();
_trackSearch->hide();
_releaseSearch->show();
_trackReleaseView->hide();
playQueue->playNext();
}));
_releaseSearch->releaseSelected().connect(std::bind([=] (Release::id_type releaseId)
audioPlayer->playPrevious().connect(std::bind([=]
{
_artistSearch->hide();
_releaseSearch->hide();
_trackSearch->hide();
_trackReleaseView->show();
_trackReleaseView->search(SearchFilter::ById(SearchFilter::Field::Release, releaseId), 40);
}, std::placeholders::_1));
_trackSearch->moreSelected().connect(std::bind([=]
{
_artistSearch->hide();
_releaseSearch->hide();
_trackSearch->show();
_trackReleaseView->hide();
playQueue->playPrevious();
}));
_trackSearch->trackPlay().connect(std::bind([=] (Track::id_type id)
audioPlayer->playbackEnded().connect(std::bind([=]
{
playTrack(audioPlayer, id);
playQueue->playNext();
}));
audioPlayer->shuffle().connect(std::bind(&PlayQueue::setShuffle, playQueue, std::placeholders::_1));
audioPlayer->loop().connect(std::bind(&PlayQueue::setLoop, playQueue, std::placeholders::_1));
// Connect the events to the player
_playQueueEvents.trackPlay.connect(std::bind([=] (Database::Track::id_type id)
{
std::size_t pos = playQueue->addTrack(id);
playQueue->play(pos);
}, std::placeholders::_1));
_trackReleaseView->trackPlay().connect(std::bind([=] (Track::id_type id)
_playQueueEvents.trackAdd.connect(std::bind([=] (Database::Track::id_type id)
{
playTrack(audioPlayer, id);
playQueue->addTrack(id);
}, std::placeholders::_1));
// Initially, populate the widgets using an empty search
{
_artistSearch->search(SearchFilter(), SEARCH_NB_ITEMS);
_releaseSearch->search(SearchFilter(), SEARCH_NB_ITEMS);
_trackSearch->search(SearchFilter(), SEARCH_NB_ITEMS);
}
}
+5 -11
View File
@@ -17,17 +17,14 @@
* along with LMS. If not, see <http://www.gnu.org/licenses/>.
*/
#ifndef UI_AUDIO_MOBILE_HPP
#define UI_AUDIO_MOBILE_HPP
#pragma once
#include <Wt/WContainerWidget>
#include "audio/Audio.hpp"
#include "ArtistSearch.hpp"
#include "ReleaseSearch.hpp"
#include "TrackSearch.hpp"
#include "TrackReleaseView.hpp"
#include "MobilePlayQueueEvents.hpp"
namespace UserInterface {
namespace Mobile {
@@ -41,13 +38,10 @@ class Audio : public UserInterface::Audio
private:
ArtistSearch* _artistSearch;
ReleaseSearch* _releaseSearch;
TrackSearch* _trackSearch;
TrackReleaseView* _trackReleaseView;
PlayQueueEvents _playQueueEvents;
};
} // namespace Mobile
} // namespace UserInterface
#endif
+206
View File
@@ -0,0 +1,206 @@
/*
* Copyright (C) 2016 Emeric Poupon
*
* This file is part of LMS.
*
* LMS is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* LMS is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with LMS. If not, see <http://www.gnu.org/licenses/>.
*/
#include <Wt/WTemplate>
#include <Wt/WImage>
#include <Wt/WText>
#include <Wt/WPushButton>
#include "logger/Logger.hpp"
#include "LmsApplication.hpp"
#include "MobilePlayQueue.hpp"
namespace UserInterface {
namespace Mobile {
PlayQueue::PlayQueue(Wt::WContainerWidget* parent)
: Wt::WContainerWidget(parent)
{
Wt::WTemplate* t = new Wt::WTemplate(this);
t->setTemplateText(Wt::WString::tr("wa-playqueue-view"));
_trackContainer = new Wt::WContainerWidget();
t->bindWidget("contents", _trackContainer);
Wt::WTemplate* controls = new Wt::WTemplate();
t->bindWidget("controls", controls);
controls->setTemplateText(Wt::WString::tr("wa-playqueue-controls"));
Wt::WText *loadBtn = new Wt::WText("<i class=\"fa fa-upload fa-2x\"></i>", Wt::XHTMLText);
controls->bindWidget("load", loadBtn);
loadBtn->setStyleClass("mobile-btn");
Wt::WText *saveBtn = new Wt::WText("<i class=\"fa fa-download fa-2x\"></i>", Wt::XHTMLText);
controls->bindWidget("save", saveBtn);
saveBtn->setStyleClass("mobile-btn");
Wt::WText *clearBtn = new Wt::WText("<i class=\"fa fa-remove fa-2x\"></i>", Wt::XHTMLText);
controls->bindWidget("clear", clearBtn);
clearBtn->setStyleClass("mobile-btn");
clearBtn->clicked().connect(std::bind([=]
{
clear();
}));
_shuffleBtn = new Wt::WText("<i class=\"fa fa-random fa-2x\"></i>", Wt::XHTMLText);
controls->bindWidget("shuffle", _shuffleBtn);
_shuffleBtn->setStyleClass("mobile-btn");
_shuffleBtn->clicked().connect(std::bind([=] ()
{
setShuffle(!_shuffle);
}));
_loopBtn = new Wt::WText("<i class=\"fa fa-repeat fa-2x\"></i>", Wt::XHTMLText);
controls->bindWidget("repeat", _loopBtn);
_loopBtn->setStyleClass("mobile-btn");
_loopBtn->clicked().connect(std::bind([=] ()
{
setLoop(!_loop);
}));
}
std::size_t
PlayQueue::addTrack(Database::Track::id_type id)
{
Wt::Dbo::Transaction transaction(DboSession());
Database::Track::pointer track = Database::Track::getById(DboSession(), id);
if (!track)
{
LMS_LOG(UI, INFO) << "No track found for id " << id;
return 0;
}
std::size_t trackPos = _trackIds.size();
_trackIds.push_back(id);
Wt::WTemplate* t = new Wt::WTemplate(_trackContainer);
t->setTemplateText(Wt::WString::tr("wa-playqueue-track"));
Wt::WImage *cover = new Wt::WImage();
t->bindWidget("cover", cover);
cover->setStyleClass ("center-block img-responsive");
cover->setImageLink(SessionImageResource()->getTrackUrl(track.id(), 64));
t->bindString("track-name", Wt::WString::fromUTF8(track->getName()), Wt::PlainText);
t->bindString("artist-name", Wt::WString::fromUTF8(track->getArtist()->getName()), Wt::PlainText);
Wt::WText *playBtn = new Wt::WText("<i class=\"fa fa-play fa-lg\"></i>", Wt::XHTMLText);
t->bindWidget("play-btn", playBtn);
playBtn->addStyleClass("mobile-btn");
playBtn->clicked().connect(std::bind([=]
{
int pos = _trackContainer->indexOf(t);
play(pos);
}));
Wt::WText *delBtn = new Wt::WText("<i class=\"fa fa-remove fa-lg\"></i>", Wt::XHTMLText);
t->bindWidget("del-btn", delBtn);
delBtn->addStyleClass("mobile-btn");
delBtn->clicked().connect(std::bind([=]
{
int pos = _trackContainer->indexOf(t);
_trackContainer->removeWidget(t);
_trackIds.erase(_trackIds.begin() + pos);
}));
return trackPos;
}
void
PlayQueue::clear(void)
{
_trackIds.clear();
_trackContainer->clear();
_currentPos = 0;
}
void
PlayQueue::play(size_t pos)
{
if (_trackIds.empty() || pos >= _trackIds.size())
return;
_trackContainer->widget(_currentPos)->removeStyleClass("playqueue-playing");
_currentPos = pos;
_trackContainer->widget(_currentPos)->addStyleClass("playqueue-playing");
_sigTrackPlay.emit(_trackIds[pos]);
}
void
PlayQueue::playNext(void)
{
if (_currentPos == (_trackIds.size() - 1) && _loop)
play(0);
else
play(_currentPos + 1);
}
void
PlayQueue::playPrevious(void)
{
if (_currentPos == 0)
{
if (_loop)
{
play(_trackIds.size() - 1);
}
else
return;
}
else
play(_currentPos - 1);
}
void
PlayQueue::handlePlaybackComplete(void)
{
playNext();
}
void
PlayQueue::setShuffle(bool value)
{
_shuffle = value;
if (value)
_shuffleBtn->addStyleClass("mobile-btn-active");
else
_shuffleBtn->removeStyleClass("mobile-btn-active");
}
void
PlayQueue::setLoop(bool value)
{
_loop = value;
if (value)
_loopBtn->addStyleClass("mobile-btn-active");
else
_loopBtn->removeStyleClass("mobile-btn-active");
}
} // namespace Mobile
} // namespace UserInterface
+75
View File
@@ -0,0 +1,75 @@
/*
* Copyright (C) 2016 Emeric Poupon
*
* This file is part of LMS.
*
* LMS is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* LMS is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with LMS. If not, see <http://www.gnu.org/licenses/>.
*/
#pragma once
#include <vector>
#include <Wt/WContainerWidget>
#include <Wt/WSignal>
#include "database/Types.hpp"
namespace UserInterface {
namespace Mobile {
class PlayQueue : public Wt::WContainerWidget
{
public:
PlayQueue(Wt::WContainerWidget* parent = 0);
// Contents
std::size_t addArtist(Database::Artist::id_type id);
std::size_t addRelease(Database::Release::id_type id);
std::size_t addTrack(Database::Track::id_type id);
void clear(void);
// Controls
void play(size_t pos); // Play the track at a given position
void playNext(void);
void playPrevious(void);
void setShuffle(bool enable); // true to enable shuffle
void setLoop(bool enable); // true to enable loop
// Signals
// Emitted when a song has to be played
Wt::Signal<Database::Track::id_type>& playTrack() { return _sigTrackPlay; }
// Slots
// Song has finished
void handlePlaybackComplete(void);
private:
bool _loop = false;
bool _shuffle = false;
std::size_t _currentPos = 0;
Wt::WText* _shuffleBtn = nullptr;
Wt::WText* _loopBtn = nullptr;
Wt::Signal<Database::Track::id_type> _sigTrackPlay;
std::vector<Database::Track::id_type> _trackIds;
Wt::WContainerWidget* _trackContainer;
};
} // namespace Mobile
} // namespace UserInterface
@@ -0,0 +1,36 @@
/*
* Copyright (C) 2016 Emeric Poupon
*
* This file is part of LMS.
*
* LMS is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* LMS is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with LMS. If not, see <http://www.gnu.org/licenses/>.
*/
#pragma once
#include <Wt/WSignal>
#include "database/Types.hpp"
namespace UserInterface {
namespace Mobile {
struct PlayQueueEvents
{
Wt::Signal<Database::Track::id_type> trackPlay;
Wt::Signal<Database::Track::id_type> trackAdd;
};
} // namespace Mobile
} // namespace UserInterface
+87
View File
@@ -0,0 +1,87 @@
/*
* 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 <http://www.gnu.org/licenses/>.
*/
#include <Wt/WApplication>
#include <Wt/WText>
#include "logger/Logger.hpp"
#include "SearchUtils.hpp"
#include "ArtistSearch.hpp"
#include "ReleaseSearch.hpp"
#include "TrackSearch.hpp"
#include "PreviewSearchView.hpp"
namespace UserInterface {
namespace Mobile {
#define SEARCH_NB_ITEMS 4
using namespace Database;
PreviewSearchView::PreviewSearchView(PlayQueueEvents& events, Wt::WContainerWidget* parent)
{
const std::string pathPrefix = "/audio/search/preview";
ArtistSearch* artistSearch = new ArtistSearch("Artists", this);
ReleaseSearch* releaseSearch = new ReleaseSearch(this);
TrackSearch* trackSearch = new TrackSearch(events, this);
artistSearch->showMore().connect(std::bind([=]
{
std::string path = wApp->internalPath();
path.replace(0, pathPrefix.length(), "/audio/search/artist");
wApp->setInternalPath(path, true);
}));
releaseSearch->showMore().connect(std::bind([=]
{
std::string path = wApp->internalPath();
path.replace(0, pathPrefix.length(), "/audio/search/release");
wApp->setInternalPath(path, true);
}));
trackSearch->showMore().connect(std::bind([=]
{
std::string path = wApp->internalPath();
path.replace(0, pathPrefix.length(), "/audio/search/track");
wApp->setInternalPath(path, true);
}));
wApp->internalPathChanged().connect(std::bind([=] (std::string path)
{
if (!wApp->internalPathMatches(pathPrefix))
return;
std::vector<std::string> keywords = searchPathToSearchKeywords(path.substr(pathPrefix.length()));
artistSearch->search(SearchFilter::ByNameAnd(SearchFilter::Field::Artist, keywords), SEARCH_NB_ITEMS);
releaseSearch->search(SearchFilter::ByNameAnd(SearchFilter::Field::Release, keywords), SEARCH_NB_ITEMS, "Releases");
trackSearch->search(SearchFilter::ByNameAnd(SearchFilter::Field::Track, keywords), SEARCH_NB_ITEMS);
}, std::placeholders::_1));
}
} // namespace Mobile
} // namespace UserInterface
+39
View File
@@ -0,0 +1,39 @@
/*
* 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 <http://www.gnu.org/licenses/>.
*/
#pragma once
#include <Wt/WContainerWidget>
#include "MobilePlayQueueEvents.hpp"
namespace UserInterface {
namespace Mobile {
class PreviewSearchView : public Wt::WContainerWidget
{
public:
PreviewSearchView(PlayQueueEvents& events, Wt::WContainerWidget* parent = 0);
};
} // namespace Mobile
} // namespace UserInterface
+62 -64
View File
@@ -26,6 +26,8 @@
#include "LmsApplication.hpp"
#include "ReleaseSearch.hpp"
#include "ReleaseView.hpp"
#include "ArtistView.hpp"
namespace UserInterface {
namespace Mobile {
@@ -33,103 +35,99 @@ namespace Mobile {
using namespace Database;
ReleaseSearch::ReleaseSearch(Wt::WContainerWidget *parent)
: Wt::WContainerWidget(parent),
_resCount(0)
: Wt::WContainerWidget(parent)
{
Wt::WTemplate* title = new Wt::WTemplate(this);
title->setTemplateText(Wt::WString::tr("mobile-search-title"));
Wt::WTemplate* t = new Wt::WTemplate(this);
t->setTemplateText(Wt::WString::tr("wa-release-search"));
title->bindWidget("text", new Wt::WText("Releases", Wt::PlainText));
Wt::WTemplate *titleTemplate = new Wt::WTemplate(this);
titleTemplate->setTemplateText(Wt::WString::tr("mobile-search-title"));
_title = new Wt::WText();
titleTemplate->bindWidget("text", _title);
t->bindWidget("title", titleTemplate);
Wt::WTemplate* releaseWrapper = new Wt::WTemplate(this);
releaseWrapper->setTemplateText(Wt::WString::tr("wa-release-wrapper"));
_contents = new Wt::WContainerWidget();
releaseWrapper->bindWidget("contents", _contents );
t->bindWidget("contents", _contents );
_showMore = new Wt::WTemplate();
_showMore->setTemplateText(Wt::WString::tr("mobile-search-more"));
_showMore->bindString("text", "Tap to show more results...");
_showMore->hide();
_showMore->clicked().connect(std::bind([=] {
_sigShowMore.emit();
}));
t->bindWidget("show-more", _showMore);
}
void
ReleaseSearch::clear()
{
while (count() > 1)
removeWidget(this->widget(1));
_resCount = 0;
_contents->clear();
_showMore->hide();
}
void
ReleaseSearch::search(Database::SearchFilter filter, size_t max)
ReleaseSearch::search(Database::SearchFilter filter, size_t max, Wt::WString title)
{
_filter = filter;
_title->setText(title);
clear();
addResults(filter, max);
}
static Wt::WString
getArtistFromRelease(Release::pointer release)
{
auto artists = Artist::getByFilter(DboSession(),
SearchFilter::ById(SearchFilter::Field::Release, release.id()), -1, 2);
if (artists.size() > 1)
return Wt::WString::fromUTF8("Various artists", Wt::PlainText);
else
return Wt::WString::fromUTF8(artists.front()->getName(), Wt::PlainText);
addResults(max);
}
void
ReleaseSearch::addResults(Database::SearchFilter filter, size_t nb)
ReleaseSearch::addResults(size_t nb)
{
using namespace Database;
Wt::Dbo::Transaction transaction(DboSession());
std::vector<Release::pointer> releases = Release::getByFilter(DboSession(), filter, _resCount, nb + 1);
bool expectMoreResults;
if (releases.size() == nb + 1)
{
expectMoreResults = true;
releases.pop_back();
}
else
expectMoreResults = false;
bool moreResults;
std::vector<Release::pointer> releases = Release::getByFilter(DboSession(), _filter, _contents->count(), nb, moreResults);
for (Release::pointer release : releases)
{
Wt::WTemplate* releaseWidget = new Wt::WTemplate(this);
releaseWidget->setTemplateText(Wt::WString::tr("wa-release-res"));
Wt::WTemplate* res = new Wt::WTemplate(_contents);
res->setTemplateText(Wt::WString::tr("wa-release-search-res"));
Wt::WImage *cover = new Wt::WImage();
Wt::WAnchor *coverAnchor = new Wt::WAnchor(ReleaseView::getLink(release.id()));
Wt::WImage *cover = new Wt::WImage(coverAnchor);
cover->setStyleClass("center-block");
cover->setImageLink( Wt::WLink( LmsApplication::instance()->getCoverResource()->getReleaseUrl(release.id(), 512)));
cover->setImageLink( SessionImageResource()->getReleaseUrl(release.id(), 512));
cover->setStyleClass("release_res_shadow release_img-responsive"); // TODO move?
releaseWidget->bindWidget("cover", cover);
releaseWidget->bindWidget("name", new Wt::WText(Wt::WString::fromUTF8(release->getName()), Wt::PlainText));
releaseWidget->bindString("release_name", Wt::WString::fromUTF8(release->getName()), Wt::PlainText);
releaseWidget->bindString("artist", getArtistFromRelease(release));
releaseWidget->clicked().connect(std::bind([=] {
_sigReleaseSelected(release.id());
}));
res->bindWidget("cover", coverAnchor);
res->bindWidget("name", new Wt::WText(Wt::WString::fromUTF8(release->getName()), Wt::PlainText));
res->bindString("release_name", Wt::WString::fromUTF8(release->getName()), Wt::PlainText);
bool variousArtists;
auto artists = Artist::getByFilter(DboSession(),
SearchFilter::ById(SearchFilter::Field::Release, release.id()), 0, 1, variousArtists);
if (!artists.empty())
{
if (variousArtists)
{
res->bindWidget("artist", new Wt::WText(Wt::WString::fromUTF8("Various Artists", Wt::PlainText)));
}
else
{
Wt::WAnchor *artistAnchor = new Wt::WAnchor(ArtistView::getLink(artists.front().id()));
Wt::WText *artist = new Wt::WText(artistAnchor);
artist->setText(Wt::WString::fromUTF8(artists.front()->getName(), Wt::PlainText));
res->bindWidget("artist", artistAnchor);
}
}
}
_resCount += releases.size();;
if (moreResults)
_showMore->show();
else
_showMore->hide();
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
+14 -12
View File
@@ -17,11 +17,13 @@
* along with LMS. If not, see <http://www.gnu.org/licenses/>.
*/
#ifndef UI_MOBILE_RELEASE_SEARCH_HPP
#define UI_MOBILE_RELEASE_SEARCH_HPP
#pragma once
#include <Wt/WContainerWidget>
#include <Wt/WSignal>
#include <Wt/WText>
#include "database/SearchFilter.hpp"
#include "database/Types.hpp"
namespace UserInterface {
namespace Mobile {
@@ -32,25 +34,25 @@ class ReleaseSearch : public Wt::WContainerWidget
ReleaseSearch(Wt::WContainerWidget *parent = 0);
void search(Database::SearchFilter filter, size_t nb);
void search(Database::SearchFilter filter, std::size_t nb, Wt::WString title);
void addResults(std::size_t nb);
// Slots
Wt::Signal<Database::Release::id_type>& releaseSelected() { return _sigReleaseSelected;}
Wt::Signal<void>& moreReleasesSelected() { return _sigMoreReleasesSelected;}
Wt::Signal<void>& showMore() { return _sigShowMore;}
private:
Wt::Signal<Database::Release::id_type> _sigReleaseSelected;
Wt::Signal<void> _sigMoreReleasesSelected;
Wt::Signal<void> _sigShowMore;
void clear(void);
void addResults(Database::SearchFilter filter, size_t nb);
Wt::WContainerWidget* _contents;
std::size_t _resCount;
Wt::WTemplate* _showMore;
Database::SearchFilter _filter;
Wt::WContainerWidget* _contents;
Wt::WText* _title;
std::size_t _count;
};
} // namespace Mobile
} // namespace UserInterface
#endif
+58
View File
@@ -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 <http://www.gnu.org/licenses/>.
*/
#include <Wt/WApplication>
#include "SearchUtils.hpp"
#include "ReleaseSearch.hpp"
#include "ReleaseSearchView.hpp"
namespace UserInterface {
namespace Mobile {
#define SEARCH_NB_ITEMS 20
using namespace Database;
ReleaseSearchView::ReleaseSearchView(Wt::WContainerWidget* parent)
{
ReleaseSearch* artistSearch = new ReleaseSearch(this);
artistSearch->showMore().connect(std::bind([=] {
artistSearch->addResults(SEARCH_NB_ITEMS);
}));
wApp->internalPathChanged().connect(std::bind([=] (std::string path)
{
const std::string pathPrefix = "/audio/search/release";
if (!wApp->internalPathMatches(pathPrefix))
return;
std::vector<std::string> keywords = searchPathToSearchKeywords(path.substr(pathPrefix.length()));
artistSearch->search(SearchFilter::ByNameAnd(SearchFilter::Field::Release, keywords), SEARCH_NB_ITEMS, "Releases");
}, std::placeholders::_1));
}
} // namespace Mobile
} // namespace UserInterface
+37
View File
@@ -0,0 +1,37 @@
/*
* Copyright (C) 2016 Emeric Poupon
*
* This file is part of LMS.
*
* LMS is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* LMS is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with LMS. If not, see <http://www.gnu.org/licenses/>.
*/
#pragma once
#include <Wt/WContainerWidget>
namespace UserInterface {
namespace Mobile {
class ReleaseSearchView : public Wt::WContainerWidget
{
public:
ReleaseSearchView(Wt::WContainerWidget* parent = 0);
};
} // namespace Mobile
} // namespace UserInterface
@@ -26,15 +26,16 @@
#include "utils/Utils.hpp"
#include "LmsApplication.hpp"
#include "TrackReleaseView.hpp"
#include "ReleaseView.hpp"
namespace UserInterface {
namespace Mobile {
using namespace Database;
TrackReleaseView::TrackReleaseView(Wt::WContainerWidget *parent)
: Wt::WContainerWidget(parent)
ReleaseView::ReleaseView(PlayQueueEvents& events, Wt::WContainerWidget *parent)
: Wt::WContainerWidget(parent),
_events(events)
{
Wt::WTemplate* wrapper = new Wt::WTemplate(this);
wrapper->setTemplateText(Wt::WString::tr("wa-trackview-wrapper"));
@@ -55,13 +56,29 @@ TrackReleaseView::TrackReleaseView(Wt::WContainerWidget *parent)
_showMore->bindString("text", "Tap to show more results...");
_showMore->hide();
_showMore->clicked().connect(std::bind([=] {
_sigMoreTracksSelected();
addResults(20);
}));
wApp->internalPathChanged().connect(std::bind([=] (std::string path)
{
const std::string pathPrefix = "/audio/release/";
if (!wApp->internalPathMatches(pathPrefix))
return;
std::string strId = path.substr(pathPrefix.length());
Release::id_type id;
if (readAs(strId, id))
{
clear();
search(SearchFilter::ById(SearchFilter::Field::Release, id), 20);
}
}, std::placeholders::_1));
}
void
TrackReleaseView::clear()
ReleaseView::clear()
{
// Flush the release container
_releaseContainer->clear();
@@ -73,7 +90,7 @@ TrackReleaseView::clear()
}
void
TrackReleaseView::search(SearchFilter filter, size_t nb)
ReleaseView::search(SearchFilter filter, size_t nb)
{
_filter = filter;
@@ -81,23 +98,6 @@ TrackReleaseView::search(SearchFilter filter, size_t nb)
addResults(nb);
}
static
std::vector<Track::pointer >
getTracks(SearchFilter filter, size_t offset, size_t nb, bool &moreResults)
{
std::vector<Track::pointer > tracks = Track::getByFilter(DboSession(), filter, offset, nb + 1);
if (tracks.size() == nb + 1)
{
moreResults = true;
tracks.pop_back();
}
else
moreResults = false;
return tracks;
}
static Wt::WString
getArtistNameFromRelease(Release::pointer release)
{
@@ -106,17 +106,19 @@ getArtistNameFromRelease(Release::pointer release)
if (artists.size() > 1)
return Wt::WString::fromUTF8("Various artists", Wt::PlainText);
else
else if (artists.size() == 1)
return Wt::WString::fromUTF8(artists.front()->getName(), Wt::PlainText);
else
return Wt::WString();
}
void
TrackReleaseView::addResults(size_t nb)
ReleaseView::addResults(size_t nb)
{
Wt::Dbo::Transaction transaction(DboSession());
bool moreResults;
std::vector<Track::pointer > tracks = getTracks(_filter, _nbTracks, nb, moreResults);
std::vector<Track::pointer> tracks = Track::getByFilter(DboSession(), _filter, _nbTracks, nb, moreResults);
for (Track::pointer track : tracks)
{
@@ -134,7 +136,7 @@ TrackReleaseView::addResults(size_t nb)
Wt::WImage *cover = new Wt::WImage();
cover->setStyleClass ("center-block img-responsive"); // TODO move to CSS?
cover->setImageLink(Wt::WLink (LmsApplication::instance()->getCoverResource()->getReleaseUrl(release.id(), 512)));
cover->setImageLink(Wt::WLink (SessionImageResource()->getReleaseUrl(release.id(), 512)));
releaseContainer->bindWidget("cover", cover);
releaseContainer->bindString("artist-name", getArtistNameFromRelease(release), Wt::PlainText);
@@ -175,16 +177,23 @@ TrackReleaseView::addResults(size_t nb)
trackRes->bindString("track-name", Wt::WString::fromUTF8(track->getName()), Wt::PlainText);
// TODO, display artist name for compilation releases?
// TODO handle large duration (> 1 hour)
trackRes->bindString("time", durationToString(track->getDuration(), "%M:%S"), Wt::PlainText);
Wt::WText *playBtn = new Wt::WText("Play", Wt::PlainText);
playBtn->setStyleClass("center-block"); // TODO move to CSS?
Wt::WText *playBtn = new Wt::WText("<i class=\"fa fa-play fa-lg\"></i>", Wt::XHTMLText);
trackRes->bindWidget("play-btn", playBtn);
playBtn->setStyleClass("mobile-btn");
playBtn->clicked().connect(std::bind([=] {
_sigTrackPlay.emit(track.id());
_events.trackPlay.emit(track.id());
}));
trackRes->bindWidget("btn", playBtn);
Wt::WText *addBtn = new Wt::WText("<i class=\"fa fa-plus fa-lg\"></i>", Wt::XHTMLText);
trackRes->bindWidget("add-btn", addBtn);
addBtn->setStyleClass("mobile-btn");
addBtn->clicked().connect(std::bind([=] {
_events.trackAdd.emit(track.id());
}));
_nbTracks++;
}
@@ -194,5 +203,11 @@ TrackReleaseView::addResults(size_t nb)
_showMore->hide();
}
Wt::WLink
ReleaseView::getLink(Database::Release::id_type id)
{
return Wt::WLink(Wt::WLink::InternalPath, "/audio/release/" + std::to_string(id));
}
} // namespace Mobile
} // namespace UserInterface
@@ -17,37 +17,37 @@
* along with LMS. If not, see <http://www.gnu.org/licenses/>.
*/
#ifndef UI_MOBILE_TRACK_SEARCH_HPP
#define UI_MOBILE_TRACK_SEARCH_HPP
#pragma once
#include <Wt/WContainerWidget>
#include <Wt/WSignal>
#include "resource/CoverResource.hpp"
#include "MobilePlayQueueEvents.hpp"
namespace UserInterface {
namespace Mobile {
class TrackReleaseView : public Wt::WContainerWidget
class ReleaseView : public Wt::WContainerWidget
{
public:
TrackReleaseView(Wt::WContainerWidget *parent = 0);
static Wt::WLink getLink(Database::Release::id_type id);
ReleaseView(PlayQueueEvents& events, Wt::WContainerWidget *parent = 0);
void search(Database::SearchFilter filter, size_t nb);
// Slots
Wt::Signal<Database::Track::id_type>& trackPlay() { return _sigTrackPlay;}
Wt::Signal<void>& moreTracksSelected() { return _sigMoreTracksSelected;}
private:
Wt::Signal<Database::Track::id_type> _sigTrackPlay;
Wt::Signal<void> _sigMoreTracksSelected;
void clear(void);
void addResults(size_t nb);
PlayQueueEvents& _events;
Wt::WTemplate* _showMore;
Database::SearchFilter _filter;
@@ -64,4 +64,3 @@ class TrackReleaseView : public Wt::WContainerWidget
} // namespace Mobile
} // namespace UserInterface
#endif
+32
View File
@@ -0,0 +1,32 @@
/*
* 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 <http://www.gnu.org/licenses/>.
*/
#pragma once
#include "utils/Utils.hpp"
static inline std::vector<std::string> searchPathToSearchKeywords(std::string path)
{
if (!path.empty() && path[0] == '/')
path.erase(path.begin());
return splitString(path, " \t");
}
+30 -45
View File
@@ -33,40 +33,36 @@ namespace Mobile {
using namespace Database;
TrackSearch::TrackSearch(Wt::WContainerWidget *parent)
: Wt::WContainerWidget(parent)
TrackSearch::TrackSearch(PlayQueueEvents& events, Wt::WContainerWidget *parent)
: Wt::WContainerWidget(parent),
_events(events)
{
Wt::WTemplate* wrapper = new Wt::WTemplate(this);
wrapper->setTemplateText(Wt::WString::tr("wa-track-wrapper"));
Wt::WTemplate* t = new Wt::WTemplate(this);
t->setTemplateText(Wt::WString::tr("wa-track-search"));
Wt::WTemplate* title = new Wt::WTemplate(this);
wrapper->bindWidget("title", title);
title->setTemplateText(Wt::WString::tr("mobile-search-title"));
title->bindString("text", "Tracks", Wt::PlainText);
t->bindWidget("title", title);
_container = new Wt::WContainerWidget();
wrapper->bindWidget("track-container", _container);
_contents = new Wt::WContainerWidget();
t->bindWidget("contents", _contents);
_showMore = new Wt::WTemplate();
wrapper->bindWidget("show-more", _showMore);
_showMore->setTemplateText(Wt::WString::tr("mobile-search-more"));
_showMore->bindString("text", "Tap to show more results...");
_showMore->hide();
_showMore->clicked().connect(std::bind([=] {
_sigMoreSelected();
_sigShowMore.emit();
addResults(20);
}));
t->bindWidget("show-more", _showMore);
}
void
TrackSearch::clear()
{
// Flush the release container
_container->clear();
_nbTracks = 0;
_contents->clear();
_showMore->hide();
}
@@ -79,52 +75,41 @@ TrackSearch::search(SearchFilter filter, size_t nb)
addResults(nb);
}
static
std::vector<Track::pointer >
getTracks(SearchFilter filter, size_t offset, size_t nb, bool &moreResults)
{
std::vector<Track::pointer > tracks = Track::getByFilter(DboSession(), filter, offset, nb + 1);
if (tracks.size() == nb + 1)
{
moreResults = true;
tracks.pop_back();
}
else
moreResults = false;
return tracks;
}
void
TrackSearch::addResults(size_t nb)
{
Wt::Dbo::Transaction transaction(DboSession());
bool moreResults;
std::vector<Track::pointer > tracks = getTracks(_filter, _nbTracks, nb, moreResults);
std::vector<Track::pointer > tracks = Track::getByFilter(DboSession(), _filter, _contents->count(), nb, moreResults);
for (Track::pointer track : tracks)
{
Wt::WTemplate* trackRes = new Wt::WTemplate(_container);
trackRes->setTemplateText(Wt::WString::tr("wa-track"));
Wt::WTemplate* res = new Wt::WTemplate(_contents);
res->setTemplateText(Wt::WString::tr("wa-track-search-res"));
Wt::WImage *cover = new Wt::WImage();
res->bindWidget("cover", cover);
cover->setStyleClass ("center-block img-responsive");
cover->setImageLink(Wt::WLink(LmsApplication::instance()->getCoverResource()->getTrackUrl(track.id(), 128)));
trackRes->bindWidget("cover", cover);
cover->setImageLink(SessionImageResource()->getTrackUrl(track.id(), 64));
res->bindString("track-name", Wt::WString::fromUTF8(track->getName()), Wt::PlainText);
res->bindString("artist-name", Wt::WString::fromUTF8(track->getArtist()->getName()), Wt::PlainText);
trackRes->bindString("track-name", Wt::WString::fromUTF8(track->getName()), Wt::PlainText);
trackRes->bindString("artist-name", Wt::WString::fromUTF8(track->getArtist()->getName()), Wt::PlainText);
Wt::WText *playBtn = new Wt::WText("Play", Wt::PlainText);
playBtn->setStyleClass("center-block"); // TODO move to CSS?
Wt::WText *playBtn = new Wt::WText("<i class=\"fa fa-play fa-lg\"></i>", Wt::XHTMLText);
res->bindWidget("play-btn", playBtn);
playBtn->setStyleClass("mobile-btn");
playBtn->clicked().connect(std::bind([=] {
_sigTrackPlay.emit(track.id());
_events.trackPlay.emit(track.id());
}));
trackRes->bindWidget("btn", playBtn);
_nbTracks++;
Wt::WText *addBtn = new Wt::WText("<i class=\"fa fa-plus fa-lg\"></i>", Wt::XHTMLText);
res->bindWidget("add-btn", addBtn);
addBtn->setStyleClass("mobile-btn");
addBtn->clicked().connect(std::bind([=] {
_events.trackAdd.emit(track.id());
}));
}
if (moreResults)
+12 -10
View File
@@ -22,6 +22,11 @@
#include <Wt/WContainerWidget>
#include <Wt/WSignal>
#include "database/SearchFilter.hpp"
#include "database/Types.hpp"
#include "MobilePlayQueueEvents.hpp"
namespace UserInterface {
namespace Mobile {
@@ -29,27 +34,24 @@ class TrackSearch : public Wt::WContainerWidget
{
public:
TrackSearch(Wt::WContainerWidget *parent = 0);
TrackSearch(PlayQueueEvents& events, Wt::WContainerWidget *parent = 0);
void addResults(size_t nb);
void search(Database::SearchFilter filter, size_t nb);
// Slots
Wt::Signal<Database::Track::id_type>& trackPlay() { return _sigTrackPlay;}
Wt::Signal<void>& moreSelected() { return _sigMoreSelected;}
Wt::Signal<void>& showMore() { return _sigShowMore;}
private:
Wt::Signal<Database::Track::id_type> _sigTrackPlay;
Wt::Signal<void> _sigMoreSelected;
Wt::Signal<void> _sigShowMore;
void clear(void);
void addResults(size_t nb);
Wt::WTemplate* _showMore;
PlayQueueEvents& _events;
Wt::WTemplate* _showMore;
Database::SearchFilter _filter;
std::size_t _nbTracks;
Wt::WContainerWidget* _container;
Wt::WContainerWidget* _contents;
};
} // namespace Mobile
+60
View File
@@ -0,0 +1,60 @@
/*
* Copyright (C) 2016 Emeric Poupon
*
* This file is part of LMS.
*
* LMS is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* LMS is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with LMS. If not, see <http://www.gnu.org/licenses/>.
*/
#include <Wt/WApplication>
#include <Wt/WText>
#include "logger/Logger.hpp"
#include "SearchUtils.hpp"
#include "TrackSearch.hpp"
#include "TrackSearchView.hpp"
namespace UserInterface {
namespace Mobile {
#define SEARCH_NB_ITEMS 20
using namespace Database;
TrackSearchView::TrackSearchView(PlayQueueEvents& events, Wt::WContainerWidget* parent)
{
TrackSearch* trackSearch = new TrackSearch(events, this);
trackSearch->showMore().connect(std::bind([=] {
trackSearch->addResults(SEARCH_NB_ITEMS);
}));
wApp->internalPathChanged().connect(std::bind([=] (std::string path)
{
const std::string pathPrefix = "/audio/search/track";
if (!wApp->internalPathMatches(pathPrefix))
return;
std::vector<std::string> keywords = searchPathToSearchKeywords(path.substr(pathPrefix.length()));
trackSearch->search(SearchFilter::ByNameAnd(SearchFilter::Field::Track, keywords), SEARCH_NB_ITEMS);
}, std::placeholders::_1));
}
} // namespace Mobile
} // namespace UserInterface
+38
View File
@@ -0,0 +1,38 @@
/*
* Copyright (C) 2016 Emeric Poupon
*
* This file is part of LMS.
*
* LMS is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* LMS is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with LMS. If not, see <http://www.gnu.org/licenses/>.
*/
#pragma once
#include <Wt/WContainerWidget>
#include <Wt/WSignal>
namespace UserInterface {
namespace Mobile {
class TrackSearchView : public Wt::WContainerWidget
{
public:
TrackSearchView(PlayQueueEvents& events, Wt::WContainerWidget* parent = 0);
};
} // namespace Mobile
} // namespace UserInterface
@@ -21,29 +21,31 @@
#include <Wt/Http/Response>
#include "logger/Logger.hpp"
#include "utils/Utils.hpp"
#include "LmsApplication.hpp"
#include "cover/CoverArtGrabber.hpp"
#include "CoverResource.hpp"
#include "ImageResource.hpp"
namespace UserInterface {
const std::string CoverResource::unknownCoverPath = "/images/unknown-cover.jpg";
static const std::string unknownCoverPath = "/images/unknown-cover.jpg";
static const std::string unknownArtistImagePath = "/images/unknown-artist.jpg";
CoverResource::CoverResource(Database::Handler& db, Wt::WObject *parent)
ImageResource::ImageResource(Database::Handler& db, Wt::WObject *parent)
: Wt::WResource(parent),
_db(db)
{
}
CoverResource::~CoverResource()
ImageResource::~ImageResource()
{
beingDeleted();
}
Image::Image
CoverResource::getDefaultCover(std::size_t size)
ImageResource::getDefaultCover(std::size_t size)
{
std::unique_lock<std::mutex> lock(_mutex);
@@ -65,26 +67,54 @@ CoverResource::getDefaultCover(std::size_t size)
return itCover->second;
}
Image::Image
ImageResource::getDefaultArtistImage(std::size_t size)
{
std::unique_lock<std::mutex> lock(_mutex);
auto itCover = _defaultArtistImages.find(size);
if (itCover == _defaultArtistImages.end())
{
Image::Image image;
if (!image.load( Wt::WApplication::instance()->docRoot() + unknownArtistImagePath))
throw std::runtime_error("Cannot read default artist image file");
image.scale(size);
auto res = _defaultArtistImages.insert(std::make_pair(size, image));
itCover = res.first;
}
return itCover->second;
}
std::string
CoverResource::getReleaseUrl(Database::Release::id_type releaseId, std::size_t size) const
ImageResource::getReleaseUrl(Database::Release::id_type releaseId, std::size_t size) const
{
return url() + "&releaseid=" + std::to_string(releaseId) + "&size=" + std::to_string(size);
}
std::string
CoverResource::getTrackUrl(Database::Track::id_type trackId, std::size_t size) const
ImageResource::getTrackUrl(Database::Track::id_type trackId, std::size_t size) const
{
return url() + "&trackid=" + std::to_string(trackId) + "&size=" + std::to_string(size);
}
std::string
CoverResource::getUnknownTrackUrl(size_t size) const
ImageResource::getArtistUrl(Database::Artist::id_type artistId, std::size_t size) const
{
return url() + "&artistid=" + std::to_string(artistId) + "&size=" + std::to_string(size);
}
std::string
ImageResource::getUnknownTrackUrl(size_t size) const
{
return url() + "&size=" + std::to_string(size);
}
void
CoverResource::putCover(Wt::Http::Response& response, Image::Image cover)
ImageResource::putImage(Wt::Http::Response& response, Image::Image cover)
{
std::vector<unsigned char> data;
@@ -95,44 +125,63 @@ CoverResource::putCover(Wt::Http::Response& response, Image::Image cover)
}
void
CoverResource::handleRequest(const Wt::Http::Request& request, Wt::Http::Response& response)
ImageResource::putCover(Wt::Http::Response& response, std::vector<Image::Image> covers, size_t size)
{
for (Image::Image cover : covers)
{
if (cover.scale(size))
{
putImage(response, cover);
return;
}
}
// If no cover found, just send default one
putImage(response, getDefaultCover(size));
}
void
ImageResource::handleRequest(const Wt::Http::Request& request, Wt::Http::Response& response)
{
// Retrieve parameters
const std::string *trackIdStr = request.getParameter("trackid");
const std::string *releaseIdStr = request.getParameter("releaseid");
const std::string *artistIdStr = request.getParameter("artistid");
const std::string *sizeStr = request.getParameter("size");
try
// Mandatory parameter size
if (!sizeStr)
return;
std::size_t size;
if (!readAs(*sizeStr, size) || size > maxSize)
return;
if (trackIdStr)
{
std::vector<Image::Image> covers;
// Mandatory parameter size
if (!sizeStr)
Database::Track::id_type trackId;
if (!readAs(*trackIdStr, trackId))
return;
std::size_t size = std::stol(*sizeStr);
if (size > maxSize)
return;
if (trackIdStr)
boost::filesystem::path path;
Database::Track::CoverType coverType = Database::Track::CoverType::None;
{
Database::Track::id_type trackId = std::stol(*trackIdStr);
boost::filesystem::path path;
Database::Track::CoverType coverType = Database::Track::CoverType::None;
// transactions are not thread safe
Wt::WApplication::UpdateLock lock(LmsApplication::instance());
Wt::Dbo::Transaction transaction(_db.getSession());
Database::Track::pointer track = Database::Track::getById(_db.getSession(), trackId);
if (track)
{
// transactions are not thread safe
Wt::WApplication::UpdateLock lock(LmsApplication::instance());
Wt::Dbo::Transaction transaction(_db.getSession());
Database::Track::pointer track = Database::Track::getById(_db.getSession(), trackId);
if (track)
{
coverType = track->getCoverType();
path = track->getPath();
}
coverType = track->getCoverType();
path = track->getPath();
}
}
if (!path.empty())
{
std::vector<Image::Image> covers;
switch (coverType)
{
@@ -144,33 +193,40 @@ CoverResource::handleRequest(const Wt::Http::Request& request, Wt::Http::Respons
covers = CoverArt::Grabber::instance().getFromDirectory(path.parent_path());
break;
}
putCover(response, covers, size);
return;
}
else if (releaseIdStr)
putImage(response, getDefaultCover(size));
return;
}
else if (releaseIdStr)
{
Database::Release::id_type releaseId;
if (!readAs(*releaseIdStr, releaseId))
return;
std::vector<Image::Image> covers;
// transactions are not thread safe
{
Database::Release::id_type releaseId = std::stol(*releaseIdStr);
// transactions are not thread safe
Wt::WApplication::UpdateLock lock(LmsApplication::instance());
Wt::Dbo::Transaction transaction(_db.getSession());
covers = CoverArt::Grabber::instance().getFromRelease(_db.getSession(), releaseId);
}
for (Image::Image& cover : covers)
{
if (cover.scale(size))
{
putCover(response, cover);
return;
}
}
// If no cover found, just send default one
putCover(response, getDefaultCover(size));
putCover(response, covers, size);
return;
}
catch (std::invalid_argument& e)
else if (artistIdStr)
{
LMS_LOG(UI, ERROR) << "Invalid argument: " << e.what();
putImage(response, getDefaultArtistImage(size));
return;
}
else
{
putImage(response, getDefaultCover(size));
return;
}
}
@@ -30,14 +30,13 @@
namespace UserInterface {
class CoverResource : public Wt::WResource
class ImageResource : public Wt::WResource
{
public:
static const std::string unknownCoverPath;
static const std::size_t maxSize = 512;
CoverResource(Database::Handler& db, Wt::WObject *parent = 0);
~CoverResource();
ImageResource(Database::Handler& db, Wt::WObject *parent = 0);
~ImageResource();
std::string getReleaseUrl(Database::Release::id_type releaseId, size_t size) const;
std::string getTrackUrl(Database::Track::id_type trackId, size_t size) const;
@@ -49,7 +48,9 @@ class CoverResource : public Wt::WResource
private:
Image::Image getDefaultCover(std::size_t size);
void putCover(Wt::Http::Response& response, Image::Image image);
Image::Image getDefaultArtistImage(std::size_t size);
void putImage(Wt::Http::Response& response, Image::Image image);
void putCover(Wt::Http::Response& response, std::vector<Image::Image> covers, std::size_t size);
// Used to protect transactions since they are not thread safe
std::mutex _mutex;
@@ -58,6 +59,9 @@ class CoverResource : public Wt::WResource
// Default cover for different sizes
std::map<std::size_t, Image::Image> _defaultCovers;
// Default artist images for different sizes
std::map<std::size_t, Image::Image> _defaultArtistImages;
// TODO construct a cache for covers?
};
+9 -7
View File
@@ -55,6 +55,8 @@ Settings::Settings(Wt::WContainerWidget* parent)
Wt::WMenu *menu = new Wt::WMenu(contents, Wt::Vertical);
menu->setStyleClass("nav nav-pills nav-stacked submenu");
menu->setWidth(150);
menu->setInternalPathEnabled();
menu->setInternalBasePath("/settings/");
hLayout->addWidget(menu);
hLayout->addWidget(contents, 1);
@@ -68,22 +70,22 @@ Settings::Settings(Wt::WContainerWidget* parent)
userIsAdmin = CurrentUser()->isAdmin();
}
menu->addItem("Audio", new AudioFormView());
menu->addItem("Audio", new AudioFormView())->setPathComponent("audio");
if (userIsAdmin)
{
MediaDirectories* mediaDirectory = new MediaDirectories();
mediaDirectory->changed().connect(this, &Settings::handleDatabaseDirectoriesChanged);
menu->addItem("Media Folders", mediaDirectory);
MediaDirectories* mediaDirectories = new MediaDirectories();
mediaDirectories->changed().connect(this, &Settings::handleDatabaseDirectoriesChanged);
menu->addItem("Media Folders", mediaDirectories)->setPathComponent("mediadirectories");
DatabaseFormView* databaseFormView = new DatabaseFormView();
databaseFormView->changed().connect(this, &Settings::restartDatabaseUpdateService);
menu->addItem("Database", databaseFormView);
menu->addItem("Database", databaseFormView)->setPathComponent("database");
menu->addItem("Users", new Users());
menu->addItem("Users", new Users())->setPathComponent("users");
}
else
{
menu->addItem("Account", new AccountFormView(userId));
menu->addItem("Account", new AccountFormView(userId))->setPathComponent("account");
}
}
-171
View File
@@ -1,171 +0,0 @@
!_TAG_FILE_FORMAT 2 /extended format; --format=1 will not append ;" to lines/
!_TAG_FILE_SORTED 2 /0=unsorted, 1=sorted, 2=foldcase/
!_TAG_PROGRAM_AUTHOR Darren Hiebert /dhiebert@users.sourceforge.net/
!_TAG_PROGRAM_NAME Exuberant Ctags //
!_TAG_PROGRAM_URL http://ctags.sourceforge.net /official site/
!_TAG_PROGRAM_VERSION 5.9~svn20110310 //
addDirectory /home/emericp/Documents/Progs/lms/src/ui/video/VideoDatabaseWidget.cpp /^VideoDatabaseWidget::addDirectory(const std::string& name, boost::filesystem::path path, size_t depth)$/;" f language:C++ class:UserInterface::VideoDatabaseWidget
addDirectory /home/emericp/Documents/Progs/lms/src/ui/video/VideoDatabaseWidget.hpp /^ void addDirectory(const std::string& name, boost::filesystem::path path, size_t depth);$/;" p language:C++ class:UserInterface::VideoDatabaseWidget
addHeader /home/emericp/Documents/Progs/lms/src/ui/video/VideoDatabaseWidget.cpp /^VideoDatabaseWidget::addHeader(void)$/;" f language:C++ class:UserInterface::VideoDatabaseWidget
addHeader /home/emericp/Documents/Progs/lms/src/ui/video/VideoDatabaseWidget.hpp /^ void addHeader(void);$/;" p language:C++ class:UserInterface::VideoDatabaseWidget
addStreams /home/emericp/Documents/Progs/lms/src/ui/video/VideoParametersDialog.cpp /^VideoParametersDialog::addStreams(Wt::WStringListModel* model, const std::vector<Av::Stream>& streams)$/;" f language:C++ class:UserInterface::VideoParametersDialog
addStreams /home/emericp/Documents/Progs/lms/src/ui/video/VideoParametersDialog.hpp /^ void addStreams(Wt::WStringListModel* model, const std::vector<Av::Stream>& streams);$/;" p language:C++ class:UserInterface::VideoParametersDialog
addVideo /home/emericp/Documents/Progs/lms/src/ui/video/VideoDatabaseWidget.cpp /^VideoDatabaseWidget::addVideo(const std::string& name, const boost::posix_time::time_duration& duration, const boost::filesystem::path& path)$/;" f language:C++ class:UserInterface::VideoDatabaseWidget
addVideo /home/emericp/Documents/Progs/lms/src/ui/video/VideoDatabaseWidget.hpp /^ void addVideo(const std::string& name, const boost::posix_time::time_duration& duration, const boost::filesystem::path& path);$/;" p language:C++ class:UserInterface::VideoDatabaseWidget
apply /home/emericp/Documents/Progs/lms/src/ui/video/VideoParametersDialog.hpp /^ Wt::Signal<void>& apply() { return _apply; }$/;" f language:C++ class:UserInterface::VideoParametersDialog
AvEncoding_to_WtEncoding /home/emericp/Documents/Progs/lms/src/ui/video/VideoMediaPlayerWidget.cpp /^AvEncoding_to_WtEncoding(Av::Encoding encoding)$/;" f language:C++ namespace:UserInterface
backToList /home/emericp/Documents/Progs/lms/src/ui/video/VideoWidget.hpp /^ void backToList(void);$/;" p language:C++ class:UserInterface::VideoWidget
close /home/emericp/Documents/Progs/lms/src/ui/video/VideoMediaPlayerWidget.hpp /^ Wt::Signal<void>& close() { return _close; };$/;" f language:C++ class:UserInterface::VideoMediaPlayerWidget
createStreamWidgets /home/emericp/Documents/Progs/lms/src/ui/video/VideoParametersDialog.cpp /^VideoParametersDialog::createStreamWidgets(const Wt::WString& labelString, Av::Stream::Type type, Wt::WTable* layout)$/;" f language:C++ class:UserInterface::VideoParametersDialog
createStreamWidgets /home/emericp/Documents/Progs/lms/src/ui/video/VideoParametersDialog.hpp /^ void createStreamWidgets(const Wt::WString& label, Av::Stream::Type type, Wt::WTable* layout);$/;" p language:C++ class:UserInterface::VideoParametersDialog
handleApply /home/emericp/Documents/Progs/lms/src/ui/video/VideoParametersDialog.cpp /^VideoParametersDialog::handleApply()$/;" f language:C++ class:UserInterface::VideoParametersDialog
handleApply /home/emericp/Documents/Progs/lms/src/ui/video/VideoParametersDialog.hpp /^ void handleApply(void);$/;" p language:C++ class:UserInterface::VideoParametersDialog
handleClose /home/emericp/Documents/Progs/lms/src/ui/video/VideoMediaPlayerWidget.cpp /^VideoMediaPlayerWidget::handleClose(void)$/;" f language:C++ class:UserInterface::VideoMediaPlayerWidget
handleClose /home/emericp/Documents/Progs/lms/src/ui/video/VideoMediaPlayerWidget.hpp /^ void handleClose(void);$/;" p language:C++ class:UserInterface::VideoMediaPlayerWidget
handleFullscreen /home/emericp/Documents/Progs/lms/src/ui/video/VideoMediaPlayerWidget.hpp /^ void handleFullscreen(void);$/;" p language:C++ class:UserInterface::VideoMediaPlayerWidget
handleParametersDone /home/emericp/Documents/Progs/lms/src/ui/video/VideoMediaPlayerWidget.cpp /^VideoMediaPlayerWidget::handleParametersDone(Wt::WDialog::DialogCode code)$/;" f language:C++ class:UserInterface::VideoMediaPlayerWidget
handleParametersDone /home/emericp/Documents/Progs/lms/src/ui/video/VideoMediaPlayerWidget.hpp /^ void handleParametersDone(Wt::WDialog::DialogCode);$/;" p language:C++ class:UserInterface::VideoMediaPlayerWidget
handleParametersEdit /home/emericp/Documents/Progs/lms/src/ui/video/VideoMediaPlayerWidget.cpp /^VideoMediaPlayerWidget::handleParametersEdit(void)$/;" f language:C++ class:UserInterface::VideoMediaPlayerWidget
handleParametersEdit /home/emericp/Documents/Progs/lms/src/ui/video/VideoMediaPlayerWidget.hpp /^ void handleParametersEdit(void);$/;" p language:C++ class:UserInterface::VideoMediaPlayerWidget
handlePlayOffset /home/emericp/Documents/Progs/lms/src/ui/video/VideoMediaPlayerWidget.cpp /^VideoMediaPlayerWidget::handlePlayOffset(int offsetSecs)$/;" f language:C++ class:UserInterface::VideoMediaPlayerWidget
handlePlayOffset /home/emericp/Documents/Progs/lms/src/ui/video/VideoMediaPlayerWidget.hpp /^ void handlePlayOffset(int offsetSecs);$/;" p language:C++ class:UserInterface::VideoMediaPlayerWidget
handleSliderMoved /home/emericp/Documents/Progs/lms/src/ui/video/VideoMediaPlayerWidget.cpp /^VideoMediaPlayerWidget::handleSliderMoved(int value)$/;" f language:C++ class:UserInterface::VideoMediaPlayerWidget
handleSliderMoved /home/emericp/Documents/Progs/lms/src/ui/video/VideoMediaPlayerWidget.hpp /^ void handleSliderMoved(int value);$/;" p language:C++ class:UserInterface::VideoMediaPlayerWidget
handleTimeUpdated /home/emericp/Documents/Progs/lms/src/ui/video/VideoMediaPlayerWidget.cpp /^VideoMediaPlayerWidget::handleTimeUpdated(void)$/;" f language:C++ class:UserInterface::VideoMediaPlayerWidget
handleTimeUpdated /home/emericp/Documents/Progs/lms/src/ui/video/VideoMediaPlayerWidget.hpp /^ void handleTimeUpdated(void);$/;" p language:C++ class:UserInterface::VideoMediaPlayerWidget
handleVolumeSliderMoved /home/emericp/Documents/Progs/lms/src/ui/video/VideoMediaPlayerWidget.cpp /^VideoMediaPlayerWidget::handleVolumeSliderMoved(int value)$/;" f language:C++ class:UserInterface::VideoMediaPlayerWidget
handleVolumeSliderMoved /home/emericp/Documents/Progs/lms/src/ui/video/VideoMediaPlayerWidget.hpp /^ void handleVolumeSliderMoved(int value);$/;" p language:C++ class:UserInterface::VideoMediaPlayerWidget
load /home/emericp/Documents/Progs/lms/src/ui/video/VideoMediaPlayerWidget.cpp /^VideoMediaPlayerWidget::load(const Av::MediaFile& mediaFile, Av::TranscodeParameters parameters)$/;" f language:C++ class:UserInterface::VideoMediaPlayerWidget
load /home/emericp/Documents/Progs/lms/src/ui/video/VideoMediaPlayerWidget.hpp /^ void load(const Av::MediaFile& mediaFile, Av::TranscodeParameters parameters);$/;" p language:C++ class:UserInterface::VideoMediaPlayerWidget
load /home/emericp/Documents/Progs/lms/src/ui/video/VideoParametersDialog.cpp /^VideoParametersDialog::load(const Av::MediaFile& mediaFile, Av::TranscodeParameters currentParameters)$/;" f language:C++ class:UserInterface::VideoParametersDialog
load /home/emericp/Documents/Progs/lms/src/ui/video/VideoParametersDialog.hpp /^ void load(const Av::MediaFile& mediaFile, Av::TranscodeParameters currentParameters);$/;" p language:C++ class:UserInterface::VideoParametersDialog
playVideo /home/emericp/Documents/Progs/lms/src/ui/video/VideoDatabaseWidget.hpp /^ Wt::Signal< boost::filesystem::path >& playVideo() { return _playVideo; }$/;" f language:C++ class:UserInterface::VideoDatabaseWidget
playVideo /home/emericp/Documents/Progs/lms/src/ui/video/VideoWidget.cpp /^VideoWidget::playVideo(boost::filesystem::path p)$/;" f language:C++ class:UserInterface::VideoWidget
playVideo /home/emericp/Documents/Progs/lms/src/ui/video/VideoWidget.hpp /^ void playVideo(boost::filesystem::path p);$/;" p language:C++ class:UserInterface::VideoWidget
save /home/emericp/Documents/Progs/lms/src/ui/video/VideoParametersDialog.cpp /^VideoParametersDialog::save(Av::TranscodeParameters& parameters)$/;" f language:C++ class:UserInterface::VideoParametersDialog
save /home/emericp/Documents/Progs/lms/src/ui/video/VideoParametersDialog.hpp /^ void save(Av::TranscodeParameters& parameters);$/;" p language:C++ class:UserInterface::VideoParametersDialog
search /home/emericp/Documents/Progs/lms/src/ui/video/VideoWidget.cpp /^VideoWidget::search(const std::string& searchText)$/;" f language:C++ class:UserInterface::VideoWidget
search /home/emericp/Documents/Progs/lms/src/ui/video/VideoWidget.hpp /^ void search(const std::string& searchText);$/;" p language:C++ class:UserInterface::VideoWidget
selectStream /home/emericp/Documents/Progs/lms/src/ui/video/VideoParametersDialog.cpp /^VideoParametersDialog::selectStream(const Wt::WStringListModel* model, int streamId, Wt::WComboBox* combo)$/;" f language:C++ class:UserInterface::VideoParametersDialog
selectStream /home/emericp/Documents/Progs/lms/src/ui/video/VideoParametersDialog.hpp /^ void selectStream(const Wt::WStringListModel* model, int streamId, Wt::WComboBox* combo);$/;" p language:C++ class:UserInterface::VideoParametersDialog
streamTypes /home/emericp/Documents/Progs/lms/src/ui/video/VideoParametersDialog.cpp /^static const std::list<Av::Stream::Type> streamTypes = {Av::Stream::Type::Video, Av::Stream::Type::Audio, Av::Stream::Type::Subtitle};$/;" m language:C++ namespace:UserInterface file:
updateView /home/emericp/Documents/Progs/lms/src/ui/video/VideoDatabaseWidget.cpp /^VideoDatabaseWidget::updateView(boost::filesystem::path directory, size_t depth)$/;" f language:C++ class:UserInterface::VideoDatabaseWidget
updateView /home/emericp/Documents/Progs/lms/src/ui/video/VideoDatabaseWidget.hpp /^ void updateView(boost::filesystem::path directory, size_t depth);$/;" p language:C++ class:UserInterface::VideoDatabaseWidget
UserInterface /home/emericp/Documents/Progs/lms/src/ui/video/VideoDatabaseWidget.cpp /^namespace UserInterface {$/;" n language:C++ file:
UserInterface /home/emericp/Documents/Progs/lms/src/ui/video/VideoDatabaseWidget.hpp /^namespace UserInterface {$/;" n language:C++
UserInterface /home/emericp/Documents/Progs/lms/src/ui/video/VideoMediaPlayerWidget.cpp /^namespace UserInterface {$/;" n language:C++ file:
UserInterface /home/emericp/Documents/Progs/lms/src/ui/video/VideoMediaPlayerWidget.hpp /^namespace UserInterface {$/;" n language:C++
UserInterface /home/emericp/Documents/Progs/lms/src/ui/video/VideoParametersDialog.cpp /^namespace UserInterface {$/;" n language:C++ file:
UserInterface /home/emericp/Documents/Progs/lms/src/ui/video/VideoParametersDialog.hpp /^namespace UserInterface {$/;" n language:C++
UserInterface /home/emericp/Documents/Progs/lms/src/ui/video/VideoWidget.cpp /^namespace UserInterface {$/;" n language:C++ file:
UserInterface /home/emericp/Documents/Progs/lms/src/ui/video/VideoWidget.hpp /^namespace UserInterface {$/;" n language:C++
UserInterface::AvEncoding_to_WtEncoding /home/emericp/Documents/Progs/lms/src/ui/video/VideoMediaPlayerWidget.cpp /^AvEncoding_to_WtEncoding(Av::Encoding encoding)$/;" f language:C++ namespace:UserInterface
UserInterface::streamTypes /home/emericp/Documents/Progs/lms/src/ui/video/VideoParametersDialog.cpp /^static const std::list<Av::Stream::Type> streamTypes = {Av::Stream::Type::Video, Av::Stream::Type::Audio, Av::Stream::Type::Subtitle};$/;" m language:C++ namespace:UserInterface file:
UserInterface::VideoDatabaseWidget /home/emericp/Documents/Progs/lms/src/ui/video/VideoDatabaseWidget.hpp /^class VideoDatabaseWidget : public Wt::WContainerWidget$/;" c language:C++ namespace:UserInterface
UserInterface::VideoDatabaseWidget::addDirectory /home/emericp/Documents/Progs/lms/src/ui/video/VideoDatabaseWidget.cpp /^VideoDatabaseWidget::addDirectory(const std::string& name, boost::filesystem::path path, size_t depth)$/;" f language:C++ class:UserInterface::VideoDatabaseWidget
UserInterface::VideoDatabaseWidget::addDirectory /home/emericp/Documents/Progs/lms/src/ui/video/VideoDatabaseWidget.hpp /^ void addDirectory(const std::string& name, boost::filesystem::path path, size_t depth);$/;" p language:C++ class:UserInterface::VideoDatabaseWidget
UserInterface::VideoDatabaseWidget::addHeader /home/emericp/Documents/Progs/lms/src/ui/video/VideoDatabaseWidget.cpp /^VideoDatabaseWidget::addHeader(void)$/;" f language:C++ class:UserInterface::VideoDatabaseWidget
UserInterface::VideoDatabaseWidget::addHeader /home/emericp/Documents/Progs/lms/src/ui/video/VideoDatabaseWidget.hpp /^ void addHeader(void);$/;" p language:C++ class:UserInterface::VideoDatabaseWidget
UserInterface::VideoDatabaseWidget::addVideo /home/emericp/Documents/Progs/lms/src/ui/video/VideoDatabaseWidget.cpp /^VideoDatabaseWidget::addVideo(const std::string& name, const boost::posix_time::time_duration& duration, const boost::filesystem::path& path)$/;" f language:C++ class:UserInterface::VideoDatabaseWidget
UserInterface::VideoDatabaseWidget::addVideo /home/emericp/Documents/Progs/lms/src/ui/video/VideoDatabaseWidget.hpp /^ void addVideo(const std::string& name, const boost::posix_time::time_duration& duration, const boost::filesystem::path& path);$/;" p language:C++ class:UserInterface::VideoDatabaseWidget
UserInterface::VideoDatabaseWidget::playVideo /home/emericp/Documents/Progs/lms/src/ui/video/VideoDatabaseWidget.hpp /^ Wt::Signal< boost::filesystem::path >& playVideo() { return _playVideo; }$/;" f language:C++ class:UserInterface::VideoDatabaseWidget
UserInterface::VideoDatabaseWidget::updateView /home/emericp/Documents/Progs/lms/src/ui/video/VideoDatabaseWidget.cpp /^VideoDatabaseWidget::updateView(boost::filesystem::path directory, size_t depth)$/;" f language:C++ class:UserInterface::VideoDatabaseWidget
UserInterface::VideoDatabaseWidget::updateView /home/emericp/Documents/Progs/lms/src/ui/video/VideoDatabaseWidget.hpp /^ void updateView(boost::filesystem::path directory, size_t depth);$/;" p language:C++ class:UserInterface::VideoDatabaseWidget
UserInterface::VideoDatabaseWidget::VideoDatabaseWidget /home/emericp/Documents/Progs/lms/src/ui/video/VideoDatabaseWidget.cpp /^VideoDatabaseWidget::VideoDatabaseWidget(Wt::WContainerWidget *parent)$/;" f language:C++ class:UserInterface::VideoDatabaseWidget
UserInterface::VideoDatabaseWidget::VideoDatabaseWidget /home/emericp/Documents/Progs/lms/src/ui/video/VideoDatabaseWidget.hpp /^ VideoDatabaseWidget(Wt::WContainerWidget *parent = 0);$/;" p language:C++ class:UserInterface::VideoDatabaseWidget
UserInterface::VideoDatabaseWidget::_playVideo /home/emericp/Documents/Progs/lms/src/ui/video/VideoDatabaseWidget.hpp /^ Wt::Signal< boost::filesystem::path > _playVideo;$/;" m language:C++ class:UserInterface::VideoDatabaseWidget
UserInterface::VideoDatabaseWidget::_table /home/emericp/Documents/Progs/lms/src/ui/video/VideoDatabaseWidget.hpp /^ Wt::WTable* _table;$/;" m language:C++ class:UserInterface::VideoDatabaseWidget
UserInterface::VideoMediaPlayerWidget /home/emericp/Documents/Progs/lms/src/ui/video/VideoMediaPlayerWidget.hpp /^class VideoMediaPlayerWidget : public Wt::WContainerWidget$/;" c language:C++ namespace:UserInterface
UserInterface::VideoMediaPlayerWidget::close /home/emericp/Documents/Progs/lms/src/ui/video/VideoMediaPlayerWidget.hpp /^ Wt::Signal<void>& close() { return _close; };$/;" f language:C++ class:UserInterface::VideoMediaPlayerWidget
UserInterface::VideoMediaPlayerWidget::handleClose /home/emericp/Documents/Progs/lms/src/ui/video/VideoMediaPlayerWidget.cpp /^VideoMediaPlayerWidget::handleClose(void)$/;" f language:C++ class:UserInterface::VideoMediaPlayerWidget
UserInterface::VideoMediaPlayerWidget::handleClose /home/emericp/Documents/Progs/lms/src/ui/video/VideoMediaPlayerWidget.hpp /^ void handleClose(void);$/;" p language:C++ class:UserInterface::VideoMediaPlayerWidget
UserInterface::VideoMediaPlayerWidget::handleFullscreen /home/emericp/Documents/Progs/lms/src/ui/video/VideoMediaPlayerWidget.hpp /^ void handleFullscreen(void);$/;" p language:C++ class:UserInterface::VideoMediaPlayerWidget
UserInterface::VideoMediaPlayerWidget::handleParametersDone /home/emericp/Documents/Progs/lms/src/ui/video/VideoMediaPlayerWidget.cpp /^VideoMediaPlayerWidget::handleParametersDone(Wt::WDialog::DialogCode code)$/;" f language:C++ class:UserInterface::VideoMediaPlayerWidget
UserInterface::VideoMediaPlayerWidget::handleParametersDone /home/emericp/Documents/Progs/lms/src/ui/video/VideoMediaPlayerWidget.hpp /^ void handleParametersDone(Wt::WDialog::DialogCode);$/;" p language:C++ class:UserInterface::VideoMediaPlayerWidget
UserInterface::VideoMediaPlayerWidget::handleParametersEdit /home/emericp/Documents/Progs/lms/src/ui/video/VideoMediaPlayerWidget.cpp /^VideoMediaPlayerWidget::handleParametersEdit(void)$/;" f language:C++ class:UserInterface::VideoMediaPlayerWidget
UserInterface::VideoMediaPlayerWidget::handleParametersEdit /home/emericp/Documents/Progs/lms/src/ui/video/VideoMediaPlayerWidget.hpp /^ void handleParametersEdit(void);$/;" p language:C++ class:UserInterface::VideoMediaPlayerWidget
UserInterface::VideoMediaPlayerWidget::handlePlayOffset /home/emericp/Documents/Progs/lms/src/ui/video/VideoMediaPlayerWidget.cpp /^VideoMediaPlayerWidget::handlePlayOffset(int offsetSecs)$/;" f language:C++ class:UserInterface::VideoMediaPlayerWidget
UserInterface::VideoMediaPlayerWidget::handlePlayOffset /home/emericp/Documents/Progs/lms/src/ui/video/VideoMediaPlayerWidget.hpp /^ void handlePlayOffset(int offsetSecs);$/;" p language:C++ class:UserInterface::VideoMediaPlayerWidget
UserInterface::VideoMediaPlayerWidget::handleSliderMoved /home/emericp/Documents/Progs/lms/src/ui/video/VideoMediaPlayerWidget.cpp /^VideoMediaPlayerWidget::handleSliderMoved(int value)$/;" f language:C++ class:UserInterface::VideoMediaPlayerWidget
UserInterface::VideoMediaPlayerWidget::handleSliderMoved /home/emericp/Documents/Progs/lms/src/ui/video/VideoMediaPlayerWidget.hpp /^ void handleSliderMoved(int value);$/;" p language:C++ class:UserInterface::VideoMediaPlayerWidget
UserInterface::VideoMediaPlayerWidget::handleTimeUpdated /home/emericp/Documents/Progs/lms/src/ui/video/VideoMediaPlayerWidget.cpp /^VideoMediaPlayerWidget::handleTimeUpdated(void)$/;" f language:C++ class:UserInterface::VideoMediaPlayerWidget
UserInterface::VideoMediaPlayerWidget::handleTimeUpdated /home/emericp/Documents/Progs/lms/src/ui/video/VideoMediaPlayerWidget.hpp /^ void handleTimeUpdated(void);$/;" p language:C++ class:UserInterface::VideoMediaPlayerWidget
UserInterface::VideoMediaPlayerWidget::handleVolumeSliderMoved /home/emericp/Documents/Progs/lms/src/ui/video/VideoMediaPlayerWidget.cpp /^VideoMediaPlayerWidget::handleVolumeSliderMoved(int value)$/;" f language:C++ class:UserInterface::VideoMediaPlayerWidget
UserInterface::VideoMediaPlayerWidget::handleVolumeSliderMoved /home/emericp/Documents/Progs/lms/src/ui/video/VideoMediaPlayerWidget.hpp /^ void handleVolumeSliderMoved(int value);$/;" p language:C++ class:UserInterface::VideoMediaPlayerWidget
UserInterface::VideoMediaPlayerWidget::load /home/emericp/Documents/Progs/lms/src/ui/video/VideoMediaPlayerWidget.cpp /^VideoMediaPlayerWidget::load(const Av::MediaFile& mediaFile, Av::TranscodeParameters parameters)$/;" f language:C++ class:UserInterface::VideoMediaPlayerWidget
UserInterface::VideoMediaPlayerWidget::load /home/emericp/Documents/Progs/lms/src/ui/video/VideoMediaPlayerWidget.hpp /^ void load(const Av::MediaFile& mediaFile, Av::TranscodeParameters parameters);$/;" p language:C++ class:UserInterface::VideoMediaPlayerWidget
UserInterface::VideoMediaPlayerWidget::VideoMediaPlayerWidget /home/emericp/Documents/Progs/lms/src/ui/video/VideoMediaPlayerWidget.cpp /^VideoMediaPlayerWidget::VideoMediaPlayerWidget( const Av::MediaFile& mediaFile, Av::TranscodeParameters parameters, Wt::WContainerWidget *parent)$/;" f language:C++ class:UserInterface::VideoMediaPlayerWidget
UserInterface::VideoMediaPlayerWidget::VideoMediaPlayerWidget /home/emericp/Documents/Progs/lms/src/ui/video/VideoMediaPlayerWidget.hpp /^ VideoMediaPlayerWidget( const Av::MediaFile& mediaFile, Av::TranscodeParameters parameters, Wt::WContainerWidget *parent = 0);$/;" p language:C++ class:UserInterface::VideoMediaPlayerWidget
UserInterface::VideoMediaPlayerWidget::_close /home/emericp/Documents/Progs/lms/src/ui/video/VideoMediaPlayerWidget.hpp /^ Wt::Signal<void> _close;$/;" m language:C++ class:UserInterface::VideoMediaPlayerWidget
UserInterface::VideoMediaPlayerWidget::_currentParameters /home/emericp/Documents/Progs/lms/src/ui/video/VideoMediaPlayerWidget.hpp /^ Av::TranscodeParameters _currentParameters;$/;" m language:C++ class:UserInterface::VideoMediaPlayerWidget
UserInterface::VideoMediaPlayerWidget::_curTime /home/emericp/Documents/Progs/lms/src/ui/video/VideoMediaPlayerWidget.hpp /^ Wt::WText* _curTime;$/;" m language:C++ class:UserInterface::VideoMediaPlayerWidget
UserInterface::VideoMediaPlayerWidget::_dialog /home/emericp/Documents/Progs/lms/src/ui/video/VideoMediaPlayerWidget.hpp /^ VideoParametersDialog* _dialog;$/;" m language:C++ class:UserInterface::VideoMediaPlayerWidget
UserInterface::VideoMediaPlayerWidget::_duration /home/emericp/Documents/Progs/lms/src/ui/video/VideoMediaPlayerWidget.hpp /^ Wt::WText* _duration;$/;" m language:C++ class:UserInterface::VideoMediaPlayerWidget
UserInterface::VideoMediaPlayerWidget::_mediaInternalLink /home/emericp/Documents/Progs/lms/src/ui/video/VideoMediaPlayerWidget.hpp /^ Wt::WLink _mediaInternalLink;$/;" m language:C++ class:UserInterface::VideoMediaPlayerWidget
UserInterface::VideoMediaPlayerWidget::_mediaPlayer /home/emericp/Documents/Progs/lms/src/ui/video/VideoMediaPlayerWidget.hpp /^ Wt::WMediaPlayer* _mediaPlayer;$/;" m language:C++ class:UserInterface::VideoMediaPlayerWidget
UserInterface::VideoMediaPlayerWidget::_mediaResource /home/emericp/Documents/Progs/lms/src/ui/video/VideoMediaPlayerWidget.hpp /^ AvConvTranscodeStreamResource* _mediaResource;$/;" m language:C++ class:UserInterface::VideoMediaPlayerWidget
UserInterface::VideoMediaPlayerWidget::_pauseBtn /home/emericp/Documents/Progs/lms/src/ui/video/VideoMediaPlayerWidget.hpp /^ Wt::WPushButton* _pauseBtn;$/;" m language:C++ class:UserInterface::VideoMediaPlayerWidget
UserInterface::VideoMediaPlayerWidget::_playBtn /home/emericp/Documents/Progs/lms/src/ui/video/VideoMediaPlayerWidget.hpp /^ Wt::WPushButton* _playBtn;$/;" m language:C++ class:UserInterface::VideoMediaPlayerWidget
UserInterface::VideoMediaPlayerWidget::_timeSlider /home/emericp/Documents/Progs/lms/src/ui/video/VideoMediaPlayerWidget.hpp /^ Wt::WSlider* _timeSlider;$/;" m language:C++ class:UserInterface::VideoMediaPlayerWidget
UserInterface::VideoMediaPlayerWidget::_volumeSlider /home/emericp/Documents/Progs/lms/src/ui/video/VideoMediaPlayerWidget.hpp /^ Wt::WSlider* _volumeSlider;$/;" m language:C++ class:UserInterface::VideoMediaPlayerWidget
UserInterface::VideoParametersDialog /home/emericp/Documents/Progs/lms/src/ui/video/VideoParametersDialog.hpp /^class VideoParametersDialog : public Wt::WDialog$/;" c language:C++ namespace:UserInterface
UserInterface::VideoParametersDialog::addStreams /home/emericp/Documents/Progs/lms/src/ui/video/VideoParametersDialog.cpp /^VideoParametersDialog::addStreams(Wt::WStringListModel* model, const std::vector<Av::Stream>& streams)$/;" f language:C++ class:UserInterface::VideoParametersDialog
UserInterface::VideoParametersDialog::addStreams /home/emericp/Documents/Progs/lms/src/ui/video/VideoParametersDialog.hpp /^ void addStreams(Wt::WStringListModel* model, const std::vector<Av::Stream>& streams);$/;" p language:C++ class:UserInterface::VideoParametersDialog
UserInterface::VideoParametersDialog::apply /home/emericp/Documents/Progs/lms/src/ui/video/VideoParametersDialog.hpp /^ Wt::Signal<void>& apply() { return _apply; }$/;" f language:C++ class:UserInterface::VideoParametersDialog
UserInterface::VideoParametersDialog::createStreamWidgets /home/emericp/Documents/Progs/lms/src/ui/video/VideoParametersDialog.cpp /^VideoParametersDialog::createStreamWidgets(const Wt::WString& labelString, Av::Stream::Type type, Wt::WTable* layout)$/;" f language:C++ class:UserInterface::VideoParametersDialog
UserInterface::VideoParametersDialog::createStreamWidgets /home/emericp/Documents/Progs/lms/src/ui/video/VideoParametersDialog.hpp /^ void createStreamWidgets(const Wt::WString& label, Av::Stream::Type type, Wt::WTable* layout);$/;" p language:C++ class:UserInterface::VideoParametersDialog
UserInterface::VideoParametersDialog::handleApply /home/emericp/Documents/Progs/lms/src/ui/video/VideoParametersDialog.cpp /^VideoParametersDialog::handleApply()$/;" f language:C++ class:UserInterface::VideoParametersDialog
UserInterface::VideoParametersDialog::handleApply /home/emericp/Documents/Progs/lms/src/ui/video/VideoParametersDialog.hpp /^ void handleApply(void);$/;" p language:C++ class:UserInterface::VideoParametersDialog
UserInterface::VideoParametersDialog::load /home/emericp/Documents/Progs/lms/src/ui/video/VideoParametersDialog.cpp /^VideoParametersDialog::load(const Av::MediaFile& mediaFile, Av::TranscodeParameters currentParameters)$/;" f language:C++ class:UserInterface::VideoParametersDialog
UserInterface::VideoParametersDialog::load /home/emericp/Documents/Progs/lms/src/ui/video/VideoParametersDialog.hpp /^ void load(const Av::MediaFile& mediaFile, Av::TranscodeParameters currentParameters);$/;" p language:C++ class:UserInterface::VideoParametersDialog
UserInterface::VideoParametersDialog::save /home/emericp/Documents/Progs/lms/src/ui/video/VideoParametersDialog.cpp /^VideoParametersDialog::save(Av::TranscodeParameters& parameters)$/;" f language:C++ class:UserInterface::VideoParametersDialog
UserInterface::VideoParametersDialog::save /home/emericp/Documents/Progs/lms/src/ui/video/VideoParametersDialog.hpp /^ void save(Av::TranscodeParameters& parameters);$/;" p language:C++ class:UserInterface::VideoParametersDialog
UserInterface::VideoParametersDialog::selectStream /home/emericp/Documents/Progs/lms/src/ui/video/VideoParametersDialog.cpp /^VideoParametersDialog::selectStream(const Wt::WStringListModel* model, int streamId, Wt::WComboBox* combo)$/;" f language:C++ class:UserInterface::VideoParametersDialog
UserInterface::VideoParametersDialog::selectStream /home/emericp/Documents/Progs/lms/src/ui/video/VideoParametersDialog.hpp /^ void selectStream(const Wt::WStringListModel* model, int streamId, Wt::WComboBox* combo);$/;" p language:C++ class:UserInterface::VideoParametersDialog
UserInterface::VideoParametersDialog::VideoParametersDialog /home/emericp/Documents/Progs/lms/src/ui/video/VideoParametersDialog.cpp /^VideoParametersDialog::VideoParametersDialog(Wt::WString windowTitle, Wt::WDialog* parent)$/;" f language:C++ class:UserInterface::VideoParametersDialog
UserInterface::VideoParametersDialog::VideoParametersDialog /home/emericp/Documents/Progs/lms/src/ui/video/VideoParametersDialog.hpp /^ VideoParametersDialog(Wt::WString windowTitle, Wt::WDialog* parent = 0);$/;" p language:C++ class:UserInterface::VideoParametersDialog
UserInterface::VideoParametersDialog::_apply /home/emericp/Documents/Progs/lms/src/ui/video/VideoParametersDialog.hpp /^ Wt::Signal<void> _apply;$/;" m language:C++ class:UserInterface::VideoParametersDialog
UserInterface::VideoParametersDialog::_streamSelection /home/emericp/Documents/Progs/lms/src/ui/video/VideoParametersDialog.hpp /^ std::map<Av::Stream::Type, std::pair<Wt::WComboBox*, Wt::WStringListModel* > > _streamSelection;$/;" m language:C++ class:UserInterface::VideoParametersDialog
UserInterface::VideoWidget /home/emericp/Documents/Progs/lms/src/ui/video/VideoWidget.hpp /^class VideoWidget : public Wt::WContainerWidget$/;" c language:C++ namespace:UserInterface
UserInterface::VideoWidget::backToList /home/emericp/Documents/Progs/lms/src/ui/video/VideoWidget.hpp /^ void backToList(void);$/;" p language:C++ class:UserInterface::VideoWidget
UserInterface::VideoWidget::playVideo /home/emericp/Documents/Progs/lms/src/ui/video/VideoWidget.cpp /^VideoWidget::playVideo(boost::filesystem::path p)$/;" f language:C++ class:UserInterface::VideoWidget
UserInterface::VideoWidget::playVideo /home/emericp/Documents/Progs/lms/src/ui/video/VideoWidget.hpp /^ void playVideo(boost::filesystem::path p);$/;" p language:C++ class:UserInterface::VideoWidget
UserInterface::VideoWidget::search /home/emericp/Documents/Progs/lms/src/ui/video/VideoWidget.cpp /^VideoWidget::search(const std::string& searchText)$/;" f language:C++ class:UserInterface::VideoWidget
UserInterface::VideoWidget::search /home/emericp/Documents/Progs/lms/src/ui/video/VideoWidget.hpp /^ void search(const std::string& searchText);$/;" p language:C++ class:UserInterface::VideoWidget
UserInterface::VideoWidget::VideoWidget /home/emericp/Documents/Progs/lms/src/ui/video/VideoWidget.cpp /^VideoWidget::VideoWidget(Wt::WContainerWidget* parent )$/;" f language:C++ class:UserInterface::VideoWidget
UserInterface::VideoWidget::VideoWidget /home/emericp/Documents/Progs/lms/src/ui/video/VideoWidget.hpp /^ VideoWidget(Wt::WContainerWidget* parent = 0);$/;" p language:C++ class:UserInterface::VideoWidget
UserInterface::VideoWidget::_mediaPlayer /home/emericp/Documents/Progs/lms/src/ui/video/VideoWidget.hpp /^ VideoMediaPlayerWidget* _mediaPlayer;$/;" m language:C++ class:UserInterface::VideoWidget
UserInterface::VideoWidget::_videoDbWidget /home/emericp/Documents/Progs/lms/src/ui/video/VideoWidget.hpp /^ VideoDatabaseWidget* _videoDbWidget;$/;" m language:C++ class:UserInterface::VideoWidget
VideoDatabaseWidget /home/emericp/Documents/Progs/lms/src/ui/video/VideoDatabaseWidget.cpp /^VideoDatabaseWidget::VideoDatabaseWidget(Wt::WContainerWidget *parent)$/;" f language:C++ class:UserInterface::VideoDatabaseWidget
VideoDatabaseWidget /home/emericp/Documents/Progs/lms/src/ui/video/VideoDatabaseWidget.hpp /^ VideoDatabaseWidget(Wt::WContainerWidget *parent = 0);$/;" p language:C++ class:UserInterface::VideoDatabaseWidget
VideoDatabaseWidget /home/emericp/Documents/Progs/lms/src/ui/video/VideoDatabaseWidget.hpp /^class VideoDatabaseWidget : public Wt::WContainerWidget$/;" c language:C++ namespace:UserInterface
VideoMediaPlayerWidget /home/emericp/Documents/Progs/lms/src/ui/video/VideoMediaPlayerWidget.cpp /^VideoMediaPlayerWidget::VideoMediaPlayerWidget( const Av::MediaFile& mediaFile, Av::TranscodeParameters parameters, Wt::WContainerWidget *parent)$/;" f language:C++ class:UserInterface::VideoMediaPlayerWidget
VideoMediaPlayerWidget /home/emericp/Documents/Progs/lms/src/ui/video/VideoMediaPlayerWidget.hpp /^ VideoMediaPlayerWidget( const Av::MediaFile& mediaFile, Av::TranscodeParameters parameters, Wt::WContainerWidget *parent = 0);$/;" p language:C++ class:UserInterface::VideoMediaPlayerWidget
VideoMediaPlayerWidget /home/emericp/Documents/Progs/lms/src/ui/video/VideoMediaPlayerWidget.hpp /^class VideoMediaPlayerWidget : public Wt::WContainerWidget$/;" c language:C++ namespace:UserInterface
VideoParametersDialog /home/emericp/Documents/Progs/lms/src/ui/video/VideoParametersDialog.cpp /^VideoParametersDialog::VideoParametersDialog(Wt::WString windowTitle, Wt::WDialog* parent)$/;" f language:C++ class:UserInterface::VideoParametersDialog
VideoParametersDialog /home/emericp/Documents/Progs/lms/src/ui/video/VideoParametersDialog.hpp /^ VideoParametersDialog(Wt::WString windowTitle, Wt::WDialog* parent = 0);$/;" p language:C++ class:UserInterface::VideoParametersDialog
VideoParametersDialog /home/emericp/Documents/Progs/lms/src/ui/video/VideoParametersDialog.hpp /^class VideoParametersDialog : public Wt::WDialog$/;" c language:C++ namespace:UserInterface
VideoWidget /home/emericp/Documents/Progs/lms/src/ui/video/VideoWidget.cpp /^VideoWidget::VideoWidget(Wt::WContainerWidget* parent )$/;" f language:C++ class:UserInterface::VideoWidget
VideoWidget /home/emericp/Documents/Progs/lms/src/ui/video/VideoWidget.hpp /^ VideoWidget(Wt::WContainerWidget* parent = 0);$/;" p language:C++ class:UserInterface::VideoWidget
VideoWidget /home/emericp/Documents/Progs/lms/src/ui/video/VideoWidget.hpp /^class VideoWidget : public Wt::WContainerWidget$/;" c language:C++ namespace:UserInterface
VIDEO_DB_WIDGET_HPP /home/emericp/Documents/Progs/lms/src/ui/video/VideoDatabaseWidget.hpp 21;" d language:C++
VIDEO_WIDGET_HPP /home/emericp/Documents/Progs/lms/src/ui/video/VideoWidget.hpp 21;" d language:C++
_apply /home/emericp/Documents/Progs/lms/src/ui/video/VideoParametersDialog.hpp /^ Wt::Signal<void> _apply;$/;" m language:C++ class:UserInterface::VideoParametersDialog
_close /home/emericp/Documents/Progs/lms/src/ui/video/VideoMediaPlayerWidget.hpp /^ Wt::Signal<void> _close;$/;" m language:C++ class:UserInterface::VideoMediaPlayerWidget
_currentParameters /home/emericp/Documents/Progs/lms/src/ui/video/VideoMediaPlayerWidget.hpp /^ Av::TranscodeParameters _currentParameters;$/;" m language:C++ class:UserInterface::VideoMediaPlayerWidget
_curTime /home/emericp/Documents/Progs/lms/src/ui/video/VideoMediaPlayerWidget.hpp /^ Wt::WText* _curTime;$/;" m language:C++ class:UserInterface::VideoMediaPlayerWidget
_dialog /home/emericp/Documents/Progs/lms/src/ui/video/VideoMediaPlayerWidget.hpp /^ VideoParametersDialog* _dialog;$/;" m language:C++ class:UserInterface::VideoMediaPlayerWidget
_duration /home/emericp/Documents/Progs/lms/src/ui/video/VideoMediaPlayerWidget.hpp /^ Wt::WText* _duration;$/;" m language:C++ class:UserInterface::VideoMediaPlayerWidget
_mediaInternalLink /home/emericp/Documents/Progs/lms/src/ui/video/VideoMediaPlayerWidget.hpp /^ Wt::WLink _mediaInternalLink;$/;" m language:C++ class:UserInterface::VideoMediaPlayerWidget
_mediaPlayer /home/emericp/Documents/Progs/lms/src/ui/video/VideoMediaPlayerWidget.hpp /^ Wt::WMediaPlayer* _mediaPlayer;$/;" m language:C++ class:UserInterface::VideoMediaPlayerWidget
_mediaPlayer /home/emericp/Documents/Progs/lms/src/ui/video/VideoWidget.hpp /^ VideoMediaPlayerWidget* _mediaPlayer;$/;" m language:C++ class:UserInterface::VideoWidget
_mediaResource /home/emericp/Documents/Progs/lms/src/ui/video/VideoMediaPlayerWidget.hpp /^ AvConvTranscodeStreamResource* _mediaResource;$/;" m language:C++ class:UserInterface::VideoMediaPlayerWidget
_pauseBtn /home/emericp/Documents/Progs/lms/src/ui/video/VideoMediaPlayerWidget.hpp /^ Wt::WPushButton* _pauseBtn;$/;" m language:C++ class:UserInterface::VideoMediaPlayerWidget
_playBtn /home/emericp/Documents/Progs/lms/src/ui/video/VideoMediaPlayerWidget.hpp /^ Wt::WPushButton* _playBtn;$/;" m language:C++ class:UserInterface::VideoMediaPlayerWidget
_playVideo /home/emericp/Documents/Progs/lms/src/ui/video/VideoDatabaseWidget.hpp /^ Wt::Signal< boost::filesystem::path > _playVideo;$/;" m language:C++ class:UserInterface::VideoDatabaseWidget
_streamSelection /home/emericp/Documents/Progs/lms/src/ui/video/VideoParametersDialog.hpp /^ std::map<Av::Stream::Type, std::pair<Wt::WComboBox*, Wt::WStringListModel* > > _streamSelection;$/;" m language:C++ class:UserInterface::VideoParametersDialog
_table /home/emericp/Documents/Progs/lms/src/ui/video/VideoDatabaseWidget.hpp /^ Wt::WTable* _table;$/;" m language:C++ class:UserInterface::VideoDatabaseWidget
_timeSlider /home/emericp/Documents/Progs/lms/src/ui/video/VideoMediaPlayerWidget.hpp /^ Wt::WSlider* _timeSlider;$/;" m language:C++ class:UserInterface::VideoMediaPlayerWidget
_videoDbWidget /home/emericp/Documents/Progs/lms/src/ui/video/VideoWidget.hpp /^ VideoDatabaseWidget* _videoDbWidget;$/;" m language:C++ class:UserInterface::VideoWidget
_volumeSlider /home/emericp/Documents/Progs/lms/src/ui/video/VideoMediaPlayerWidget.hpp /^ Wt::WSlider* _volumeSlider;$/;" m language:C++ class:UserInterface::VideoMediaPlayerWidget
__VIDEO_MEDIA_PLAYER_WIDGET_HPP /home/emericp/Documents/Progs/lms/src/ui/video/VideoMediaPlayerWidget.hpp 21;" d language:C++
+12
View File
@@ -118,4 +118,16 @@ stringToUTF8(const std::string& str)
return boost::locale::conv::to_utf<char>(str, "UTF-8");
}
std::string
bufferToString(const std::vector<unsigned char>& data)
{
std::ostringstream oss;
for (unsigned char c : data)
{
oss << std::setw(2) << std::setfill('0') << std::hex << (int)c;
}
return oss.str();
}
+3
View File
@@ -44,6 +44,9 @@ stringTrim(const std::string& str, const std::string& whitespace = " \t");
std::string
stringToUTF8(const std::string& str);
std::string
bufferToString(const std::vector<unsigned char>& data);
template<typename T>
static inline bool readAs(const std::string& str, T& data)
{
+2 -2
View File
@@ -1,7 +1,7 @@
TESTS = database-basics database-integrity sql-query database-user
check_PROGRAMS = database-basics database-integrity sql-query database-user test-wt test-avmetadata test-avtranscoder test-wt-audio
check_PROGRAMS = database-basics database-integrity sql-query database-user test-wt test-avmetadata
database_basics_SOURCES = \
$(srcdir)/CheckDbBasics.cpp \
@@ -83,7 +83,7 @@ test_wt_audio_SOURCES = TestWtAudio.cpp\
$(top_srcdir)/src/ui/resource/CoverResource.cpp \
$(top_srcdir)/src/ui/resource/TranscodeResource.cpp
test_wt_audio_CXXFLAGS=-std=c++11 -Wall -Wextra -I$(top_srcdir)/src $(MAGICKXX_CFLAGS)
test_wt_audio_CXXFLAGS=-std=c++11 -Wall -Wextra -I$(top_srcdir)/src -I$(top_srcdir)/src/ui $(MAGICKXX_CFLAGS)
test_wt_audio_LDADD=$(MAGICKXX_LIBS)
+118 -1
View File
@@ -2,8 +2,62 @@
#include <Wt/WServer>
#include <Wt/WApplication>
#include <Wt/WContainerWidget>
#include <Wt/WStackedWidget>
#include <Wt/WComboBox>
#include <Wt/WText>
#include <Wt/WLineEdit>
#include <Wt/WAnchor>
#include <boost/algorithm/string/split.hpp>
#include <boost/algorithm/string.hpp>
class ArtistView : public Wt::WContainerWidget
{
public:
ArtistView(Wt::WContainerWidget *parent = 0)
: Wt::WContainerWidget(parent)
{
}
void setId(int id)
{
clear();
Wt::WText *header = new Wt::WText("Artist view ID = " + std::to_string(id), this);
header->setInline(false);
for (int i = id; i > 0; --i)
{
Wt::WAnchor *anchor = new Wt::WAnchor(Wt::WLink(Wt::WLink::InternalPath, "/release/" + std::to_string(i)), this);
Wt::WText *text = new Wt::WText("Release " + std::to_string(i), anchor);
text->setInline(false);
}
}
};
class ReleaseView : public Wt::WContainerWidget
{
public:
ReleaseView(Wt::WContainerWidget *parent = 0)
: Wt::WContainerWidget(parent)
{
}
void setId(int id)
{
clear();
Wt::WText *header = new Wt::WText("Release view ID = " + std::to_string(id), this);
header->setInline(false);
for (int i = id; i > 0; --i)
{
Wt::WAnchor *anchor = new Wt::WAnchor(Wt::WLink(Wt::WLink::InternalPath, "/artist/" + std::to_string(i)), this);
Wt::WText *text = new Wt::WText("Artist " + std::to_string(i), anchor);
text->setInline(false);
}
}
};
class TestApplication : public Wt::WApplication
{
@@ -11,7 +65,70 @@ class TestApplication : public Wt::WApplication
TestApplication(const Wt::WEnvironment& env)
: Wt::WApplication(env)
{
root()->addWidget(new Wt::WText("Hello, world!"));
enableInternalPaths();
Wt::WComboBox *combo = new Wt::WComboBox(root());
combo->addItem("Artist");
combo->addItem("Release");
Wt::WLineEdit *edit = new Wt::WLineEdit("Enter id", root());
edit->changed().connect(std::bind([=]
{
if (combo->currentText() == "Artist")
wApp->setInternalPath("/artist/" + edit->text().toUTF8(), true);
else if (combo->currentText() == "Release")
wApp->setInternalPath("/release/" + edit->text().toUTF8(), true);
}));
Wt::WStackedWidget *stack = new Wt::WStackedWidget(root());
Wt::WContainerWidget *artistContainer = new Wt::WContainerWidget();
Wt::WContainerWidget *releaseContainer = new Wt::WContainerWidget();
stack->addWidget(releaseContainer);
stack->addWidget(artistContainer);
internalPathChanged().connect(std::bind([=] (std::string path)
{
wApp->log("info") << "Path set to '" << path << "'";
std::vector<std::string> strings;
boost::algorithm::split(strings, path, boost::is_any_of("/"), boost::token_compress_on);
if (strings.size() != 3)
return;
std::string view = strings[1];
int id;
try {
id = std::stol(strings[2]);
}
catch (std::exception& e) {
return;
}
if (view == "release")
{
releaseContainer->clear();
ReleaseView *releaseView = new ReleaseView(releaseContainer);
releaseView->setId(id);
stack->setCurrentIndex(0);
}
else if (view == "artist")
{
artistContainer->clear();
ArtistView *artistView = new ArtistView(artistContainer);
artistView->setId(id);
stack->setCurrentIndex(1);
}
}, std::placeholders::_1));
setInternalPath("/main");
}
};