Merge branch 'develop'
Conflicts: README src/ui/approot/templates.xml
This commit is contained in:
@@ -1,17 +1,17 @@
|
|||||||
LMS - Lightweight Media Server
|
LMS - Lightweight Media Server
|
||||||
|
|
||||||
|
|
||||||
LMS is a free media streaming software, released under the GPLv3 license.
|
LMS is a self-hosted media streaming software, released under the GPLv3 license.
|
||||||
It allows you to access your music and your videos using an https web interface.
|
It allows you to access your music and your videos using an https web interface.
|
||||||
|
|
||||||
Features:
|
Features:
|
||||||
|
- Winamp-like interface, suited for large databases
|
||||||
|
- Audio/Video transcode for maximum interoperability and low bandwith requirements
|
||||||
- User management
|
- User management
|
||||||
- Fast "Winamp-style" interface
|
- API available in Protocol Buffers (see src/remote/proto/messages.proto)
|
||||||
- Audio/Video transcode (including downsampling), based on the libav project (see https://www.libav.org/).
|
|
||||||
- Custom API to access data using Protocol Buffers format over TCP/SSL (see https://github.com/google/protobuf/ and src/remote/proto)
|
|
||||||
|
|
||||||
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.
|
||||||
An android application is being developped as well.
|
A mobile application based on Kivy is being developped as well.
|
||||||
|
|
||||||
LMS is written entirely in C++. Therefore, it is suitable to run on embedded devices, where space and/or memory are limited.
|
LMS is written entirely in C++. Therefore, it is suitable to run on embedded devices, where space and/or memory are limited.
|
||||||
Please note some media files may require significant CPU usage to be transcoded.
|
Please note some media files may require significant CPU usage to be transcoded.
|
||||||
@@ -37,7 +37,7 @@ II) BUILD
|
|||||||
|
|
||||||
III) Setting up SSL materials (required by the SSL server and the web server)
|
III) Setting up SSL materials (required by the SSL server and the web server)
|
||||||
|
|
||||||
This is just a self-signed certificate example, you can set up a real CA if you want to.
|
This is just a self signed certificate example, you could do use a CA if you want.
|
||||||
|
|
||||||
Generate a dh file:
|
Generate a dh file:
|
||||||
# openssl dhparam -out dh2048.pem 2048
|
# openssl dhparam -out dh2048.pem 2048
|
||||||
@@ -59,3 +59,9 @@ You must also link Wt's docroot (usually located in /usr/share/Wt) into that dir
|
|||||||
|
|
||||||
Depending on your SSL parameters, you may be asked for the PEM passphrase to unlock the private key.
|
Depending on your SSL parameters, you may be asked for the PEM passphrase to unlock the private key.
|
||||||
|
|
||||||
|
V) LINKS
|
||||||
|
|
||||||
|
- Wt (http://www.webtoolkit.eu/)
|
||||||
|
- bootstrap3 (http://getbootstrap.com/)
|
||||||
|
- libav project (https://www.libav.org/).
|
||||||
|
- Protocol Buffers (https://github.com/google/protobuf/)
|
||||||
|
|||||||
@@ -22,7 +22,7 @@
|
|||||||
- OGG metadata -> properly handle metadata nested in the audio stream
|
- OGG metadata -> properly handle metadata nested in the audio stream
|
||||||
|
|
||||||
[Transcode]
|
[Transcode]
|
||||||
- some early end of playback spotted on flac files
|
- some early end of playback spotted on flac files. Spotted on old firefox versions?
|
||||||
|
|
||||||
[UI]
|
[UI]
|
||||||
- handle internationalization
|
- handle internationalization
|
||||||
@@ -31,6 +31,7 @@
|
|||||||
- "signal not exposed" problem if a user logout and login again. bad resource destruction?
|
- "signal not exposed" problem if a user logout and login again. bad resource destruction?
|
||||||
[user/transcoding]
|
[user/transcoding]
|
||||||
- Prefered codecs for audio/video?
|
- Prefered codecs for audio/video?
|
||||||
|
- use a slider instead of a combo box for the bitrates
|
||||||
[admin/Users]
|
[admin/Users]
|
||||||
- do not activate the update period/start time when 'admin' is checked
|
- do not activate the update period/start time when 'admin' is checked
|
||||||
[admin/Database update]
|
[admin/Database update]
|
||||||
@@ -39,10 +40,12 @@
|
|||||||
|
|
||||||
[Audio]
|
[Audio]
|
||||||
- Style eveything nicely...
|
- Style eveything nicely...
|
||||||
- MediaPlayer: move slider using js (http://redmine.webtoolkit.eu/boards/2/topics/7924?r=8478)
|
- MediaPlayer: move the slider using js (http://redmine.webtoolkit.eu/boards/2/topics/7924?r=8478)
|
||||||
- TrackView : handle duration > 1 hour
|
- TrackView : handle duration > 1 hour
|
||||||
- TrackView : Reselect the current selected item when displaying the updated search results
|
- TrackView : Reselect the current selected item when displaying the updated search results
|
||||||
- Add covers in the release filter?
|
- Add covers in the release filter?
|
||||||
|
- Add a download button to get the current playlist in a streamed zip file
|
||||||
|
- Add keyboard shortcuts
|
||||||
|
|
||||||
[Video]
|
[Video]
|
||||||
- implement a decent mediaplayer
|
- implement a decent mediaplayer
|
||||||
|
|||||||
@@ -23,7 +23,7 @@
|
|||||||
#include <boost/log/trivial.hpp>
|
#include <boost/log/trivial.hpp>
|
||||||
#include <boost/log/expressions.hpp>
|
#include <boost/log/expressions.hpp>
|
||||||
#include <boost/log/sinks/text_file_backend.hpp>
|
#include <boost/log/sinks/text_file_backend.hpp>
|
||||||
#include </usr/include/boost/log/keywords/filter.hpp>
|
#include <boost/log/keywords/filter.hpp>
|
||||||
#include <boost/log/utility/setup/file.hpp>
|
#include <boost/log/utility/setup/file.hpp>
|
||||||
#include <boost/log/utility/setup/common_attributes.hpp>
|
#include <boost/log/utility/setup/common_attributes.hpp>
|
||||||
#include <boost/log/utility/setup/console.hpp>
|
#include <boost/log/utility/setup/console.hpp>
|
||||||
@@ -89,6 +89,7 @@ Logger::init(const Config& config)
|
|||||||
boost::log::keywords::file_name = config.logPath + std::string(".%N"),
|
boost::log::keywords::file_name = config.logPath + std::string(".%N"),
|
||||||
boost::log::keywords::rotation_size = 10 * 1024 * 1024,
|
boost::log::keywords::rotation_size = 10 * 1024 * 1024,
|
||||||
boost::log::keywords::open_mode = std::ios_base::app,
|
boost::log::keywords::open_mode = std::ios_base::app,
|
||||||
|
boost::log::keywords::auto_flush = true,
|
||||||
boost::log::keywords::format = (
|
boost::log::keywords::format = (
|
||||||
boost::log::expressions::stream
|
boost::log::expressions::stream
|
||||||
<< boost::log::expressions::format_date_time< boost::posix_time::ptime >("TimeStamp", "[%Y-%m-%d %H:%M:%S]")
|
<< boost::log::expressions::format_date_time< boost::posix_time::ptime >("TimeStamp", "[%Y-%m-%d %H:%M:%S]")
|
||||||
|
|||||||
@@ -5,6 +5,16 @@ import "media.proto";
|
|||||||
|
|
||||||
package Remote;
|
package Remote;
|
||||||
|
|
||||||
|
// Every request sent by the client must be a ClientMessage
|
||||||
|
// The server will answer using a ServerMessage
|
||||||
|
//
|
||||||
|
// The client must authenticate first before querying data
|
||||||
|
// If an expected message is received, the connection is dropped by the server
|
||||||
|
//
|
||||||
|
// A header is to be added before the serialized protobuf message in order to keep
|
||||||
|
// track of where one message ends and the next begins.
|
||||||
|
// Format:
|
||||||
|
// MAGIC (4 bytes, 0xDE, 0xAD, 0xBE, 0xEF)- SIZE (4 bytes, big endian encoded) - serialized proto message
|
||||||
message ClientMessage
|
message ClientMessage
|
||||||
{
|
{
|
||||||
enum Type
|
enum Type
|
||||||
|
|||||||
@@ -341,9 +341,7 @@
|
|||||||
|
|
||||||
<message id="mobile-artist-res">
|
<message id="mobile-artist-res">
|
||||||
<div class="row">
|
<div class="row">
|
||||||
<div class="col-xs-12">
|
<div class="col-xs-12 mobile-search-entry">${name}</div>
|
||||||
<div class="mobile-search-entry">${name}</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</message>
|
</message>
|
||||||
|
|
||||||
|
|||||||
@@ -34,7 +34,7 @@
|
|||||||
|
|
||||||
#include "PlayQueue.hpp"
|
#include "PlayQueue.hpp"
|
||||||
|
|
||||||
static const int NameRole = Wt::UserRole;
|
static const int TrackInfoRole = Wt::UserRole;
|
||||||
|
|
||||||
namespace {
|
namespace {
|
||||||
|
|
||||||
@@ -220,10 +220,11 @@ TrackSelector::setSize(std::size_t size)
|
|||||||
refreshPositions();
|
refreshPositions();
|
||||||
}
|
}
|
||||||
|
|
||||||
struct Name
|
struct TrackInfo
|
||||||
{
|
{
|
||||||
Wt::WString track;
|
Wt::WString track;
|
||||||
Wt::WString artist;
|
Wt::WString artist;
|
||||||
|
Wt::WString release;
|
||||||
};
|
};
|
||||||
|
|
||||||
class PlayQueueItemDelegate : public Wt::WItemDelegate
|
class PlayQueueItemDelegate : public Wt::WItemDelegate
|
||||||
@@ -235,13 +236,13 @@ class PlayQueueItemDelegate : public Wt::WItemDelegate
|
|||||||
{
|
{
|
||||||
Wt::WWidget* res;
|
Wt::WWidget* res;
|
||||||
|
|
||||||
if (!index.data(NameRole).empty())
|
if (!index.data(TrackInfoRole).empty())
|
||||||
{
|
{
|
||||||
Name name = boost::any_cast<Name>(index.data(NameRole));
|
TrackInfo trackInfo = boost::any_cast<TrackInfo>(index.data(TrackInfoRole));
|
||||||
|
|
||||||
Wt::WContainerWidget *container = new Wt::WContainerWidget();
|
Wt::WContainerWidget *container = new Wt::WContainerWidget();
|
||||||
Wt::WText* track = new Wt::WText(name.track, Wt::PlainText, container);
|
Wt::WText* track = new Wt::WText(trackInfo.track, Wt::PlainText, container);
|
||||||
Wt::WText* artist = new Wt::WText(name.artist, Wt::PlainText, container);
|
Wt::WText* artist = new Wt::WText(trackInfo.artist + " - " + trackInfo.release, Wt::PlainText, container);
|
||||||
|
|
||||||
artist->setInline(false);
|
artist->setInline(false);
|
||||||
track->setInline(false);
|
track->setInline(false);
|
||||||
@@ -382,10 +383,11 @@ PlayQueue::addTracks(const std::vector<Database::Track::id_type>& trackIds)
|
|||||||
coverUrl = "images/unknown-cover.jpg";
|
coverUrl = "images/unknown-cover.jpg";
|
||||||
_model->setData(dataRow, COLUMN_ID_COVER, coverUrl, Wt::DecorationRole);
|
_model->setData(dataRow, COLUMN_ID_COVER, coverUrl, Wt::DecorationRole);
|
||||||
|
|
||||||
Name name;
|
TrackInfo trackInfo;
|
||||||
name.track = Wt::WString::fromUTF8(track->getName());
|
trackInfo.track = Wt::WString::fromUTF8(track->getName());
|
||||||
name.artist = Wt::WString::fromUTF8(track->getArtistName());
|
trackInfo.artist = Wt::WString::fromUTF8(track->getArtistName());
|
||||||
_model->setData(dataRow, COLUMN_ID_NAME, name, NameRole);
|
trackInfo.release = Wt::WString::fromUTF8(track->getReleaseName());
|
||||||
|
_model->setData(dataRow, COLUMN_ID_NAME, trackInfo, TrackInfoRole);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -45,17 +45,6 @@ VideoWidget::search(const std::string& searchText)
|
|||||||
//TODO
|
//TODO
|
||||||
}
|
}
|
||||||
|
|
||||||
void
|
|
||||||
VideoWidget::backToList(void)
|
|
||||||
{
|
|
||||||
if (_mediaPlayer)
|
|
||||||
delete _mediaPlayer;
|
|
||||||
|
|
||||||
_videoDbWidget->setHidden(false);
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
void
|
void
|
||||||
VideoWidget::playVideo(boost::filesystem::path p)
|
VideoWidget::playVideo(boost::filesystem::path p)
|
||||||
{
|
{
|
||||||
@@ -100,8 +89,11 @@ VideoWidget::playVideo(boost::filesystem::path p)
|
|||||||
parameters.setBitrate(Transcode::Stream::Audio, 0/*audioBitrate*/);
|
parameters.setBitrate(Transcode::Stream::Audio, 0/*audioBitrate*/);
|
||||||
parameters.setBitrate(Transcode::Stream::Video, 0/*videoBitrate*/);
|
parameters.setBitrate(Transcode::Stream::Video, 0/*videoBitrate*/);
|
||||||
|
|
||||||
_mediaPlayer = new VideoMediaPlayerWidget(parameters, this);
|
VideoMediaPlayerWidget *mediaPlayer = new VideoMediaPlayerWidget(parameters, this);
|
||||||
_mediaPlayer->close().connect(this, &VideoWidget::backToList);
|
mediaPlayer->close().connect(std::bind([=] () {
|
||||||
|
_videoDbWidget->setHidden(false);
|
||||||
|
delete mediaPlayer;
|
||||||
|
}));
|
||||||
|
|
||||||
_videoDbWidget->setHidden(true);
|
_videoDbWidget->setHidden(true);
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user