diff --git a/CMakeLists.txt b/CMakeLists.txt
index 74f07659..3b8c7f65 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -525,6 +525,7 @@ add_subdirectory(dist)
add_subdirectory(ext/libstrawberry-common)
add_subdirectory(ext/libstrawberry-tagreader)
add_subdirectory(ext/strawberry-tagreader)
+add_subdirectory(src/networkremote)
if(HAVE_MOODBAR)
add_subdirectory(ext/gstmoodbar)
endif()
diff --git a/data/icons.qrc b/data/icons.qrc
index 847f5c96..d52dff38 100644
--- a/data/icons.qrc
+++ b/data/icons.qrc
@@ -501,5 +501,6 @@
icons/22x22/somafm.png
icons/22x22/radioparadise.png
icons/22x22/musicbrainz.png
+ icons/32x32/network-remote.png
diff --git a/data/icons/32x32/network-remote.png b/data/icons/32x32/network-remote.png
new file mode 100644
index 00000000..ad14456d
Binary files /dev/null and b/data/icons/32x32/network-remote.png differ
diff --git a/debian/control b/debian/control
new file mode 100644
index 00000000..a0f20a47
--- /dev/null
+++ b/debian/control
@@ -0,0 +1,64 @@
+Source: strawberry
+Section: sound
+Priority: optional
+Maintainer: Jonas Kvinge
+Build-Depends: debhelper (>= 11),
+ git,
+ make,
+ cmake,
+ gcc,
+ g++,
+ protobuf-compiler,
+ libglib2.0-dev,
+ libdbus-1-dev,
+ libprotobuf-dev,
+ libboost-dev,
+ libsqlite3-dev,
+ libasound2-dev,
+ libpulse-dev,
+ libtag1-dev,
+ libicu-dev,
+ qt6-base-dev,qt6-base-dev-tools,qt6-tools-dev,qt6-tools-dev-tools,qt6-l10n-tools,
+ libgstreamer1.0-dev,
+ libgstreamer-plugins-base1.0-dev,
+ libcdio-dev,
+ libgpod-dev,
+ libmtp-dev,
+ libchromaprint-dev,
+ libfftw3-dev,
+ libebur128-dev
+Standards-Version: 4.6.1
+
+Package: strawberry
+Architecture: any
+Depends: ${shlibs:Depends},
+ ${misc:Depends},
+ libqt6sql6-sqlite,qt6-qpa-plugins,
+ gstreamer1.0-plugins-base,
+ gstreamer1.0-plugins-good,
+ gstreamer1.0-alsa,
+ gstreamer1.0-pulseaudio
+Homepage: http://www.strawberrymusicplayer.org/
+Description: music player and music collection organizer
+ Strawberry is a music player aimed at music collectors and audiophiles.
+ .
+ Features:
+ - Play and organize music
+ - Supports WAV, FLAC, WavPack, Ogg Vorbis, Speex, MPC, TrueAudio, AIFF, MP4, MP3 and ASF
+ - Audio CD playback
+ - Native desktop notifications
+ - Playlist management and playlists in multiple formats
+ - Smart and dynamic playlists
+ - Advanced audio output and device configuration for bit-perfect playback on Linux
+ - Edit tags on audio files
+ - Automatically retrieve tags from MusicBrainz
+ - Album cover art from Last.fm, Musicbrainz, Discogs, Musixmatch, Deezer, Tidal, Qobuz and Spotify
+ - Song lyrics from Genius, Musixmatch, ChartLyrics, lyrics.ovh, lololyrics.com, songlyrics.com, azlyrics.com, elyrics.net and lyricsmode.com
+ - Audio analyzer
+ - Audio equalizer
+ - Transfer music to mass-storage USB players, MTP compatible devices and iPod Nano/Classic
+ - Scrobbler with support for Last.fm, Libre.fm and ListenBrainz
+ - Streaming support for Subsonic-compatible servers
+ - Unofficial streaming support for Tidal and Qobuz
+ .
+ It is a fork of Clementine. The name is inspired by the band Strawbs.
diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt
index 89b4f097..7c8f367b 100644
--- a/src/CMakeLists.txt
+++ b/src/CMakeLists.txt
@@ -207,6 +207,7 @@ set(SOURCES
settings/appearancesettingspage.cpp
settings/contextsettingspage.cpp
settings/notificationssettingspage.cpp
+ settings/networkremotesettingspage.cpp
dialogs/about.cpp
dialogs/console.cpp
@@ -293,6 +294,13 @@ set(SOURCES
organize/organizedialog.cpp
organize/organizeerrordialog.cpp
+ networkremote/networkremote.cpp
+ networkremote/tcpserver.cpp
+ networkremote/remotesettings.cpp
+ networkremote/client.cpp
+ networkremote/clientmanager.cpp
+ networkremote/incomingmsg.cpp
+ networkremote/outgoingmsg.cpp
)
set(HEADERS
@@ -450,6 +458,7 @@ set(HEADERS
settings/appearancesettingspage.h
settings/contextsettingspage.h
settings/notificationssettingspage.h
+ settings/networkremotesettingspage.h
dialogs/about.h
dialogs/errordialog.h
@@ -532,6 +541,13 @@ set(HEADERS
organize/organizedialog.h
organize/organizeerrordialog.h
+ networkremote/networkremote.h
+ networkremote/tcpserver.h
+ networkremote/remotesettings.h
+ networkremote/client.h
+ networkremote/clientmanager.h
+ networkremote/incomingmsg.h
+ networkremote/outgoingmsg.h
)
set(UI
@@ -576,6 +592,7 @@ set(UI
settings/networkproxysettingspage.ui
settings/appearancesettingspage.ui
settings/notificationssettingspage.ui
+ settings/networkremotesettingspage.ui
equalizer/equalizer.ui
equalizer/equalizerslider.ui
@@ -1116,6 +1133,7 @@ target_link_libraries(strawberry_lib PUBLIC
${SINGLEAPPLICATION_LIBRARIES}
libstrawberry-common
libstrawberry-tagreader
+ lib-networkremote
)
if(HAVE_DBUS)
diff --git a/src/core/application.cpp b/src/core/application.cpp
index 0a813135..cb904de2 100644
--- a/src/core/application.cpp
+++ b/src/core/application.cpp
@@ -103,6 +103,8 @@
#include "radios/radioservices.h"
#include "radios/radiobackend.h"
+#include "networkremote/networkremote.h"
+
using std::make_shared;
using namespace std::chrono_literals;
@@ -203,8 +205,13 @@ class ApplicationImpl {
moodbar_loader_([app]() { return new MoodbarLoader(app); }),
moodbar_controller_([app]() { return new MoodbarController(app); }),
#endif
- lastfm_import_([app]() { return new LastFMImport(app->network()); })
- {}
+ lastfm_import_([app]() { return new LastFMImport(app->network()); }),
+ network_remote_([app]() {
+ NetworkRemote *remote = new NetworkRemote(app);
+ app->MoveToNewThread(remote);
+ return remote;
+ })
+{}
Lazy tag_reader_client_;
Lazy database_;
@@ -230,6 +237,7 @@ class ApplicationImpl {
Lazy moodbar_controller_;
#endif
Lazy lastfm_import_;
+ Lazy network_remote_;
};
@@ -239,6 +247,7 @@ Application::Application(QObject *parent)
device_finders()->Init();
collection()->Init();
tag_reader_client();
+ network_remote()->Init();
QObject::connect(&*database(), &Database::Error, this, &Application::ErrorAdded);
@@ -288,7 +297,8 @@ void Application::Exit() {
<< &*device_manager()
#endif
<< &*internet_services()
- << &*radio_services()->radio_backend();
+ << &*radio_services()->radio_backend()
+ << &*network_remote();
QObject::connect(&*tag_reader_client(), &TagReaderClient::ExitFinished, this, &Application::ExitReceived);
tag_reader_client()->ExitAsync();
@@ -357,7 +367,9 @@ SharedPtr Application::internet_services() const { return p_->
SharedPtr Application::radio_services() const { return p_->radio_services_.ptr(); }
SharedPtr Application::scrobbler() const { return p_->scrobbler_.ptr(); }
SharedPtr Application::lastfm_import() const { return p_->lastfm_import_.ptr(); }
+SharedPtr Application::network_remote() const { return p_->network_remote_.ptr();}
#ifdef HAVE_MOODBAR
SharedPtr Application::moodbar_controller() const { return p_->moodbar_controller_.ptr(); }
SharedPtr Application::moodbar_loader() const { return p_->moodbar_loader_.ptr(); }
+
#endif
diff --git a/src/core/application.h b/src/core/application.h
index 3e31e8ca..a0e96ed0 100644
--- a/src/core/application.h
+++ b/src/core/application.h
@@ -65,6 +65,8 @@ class MoodbarController;
class MoodbarLoader;
#endif
+class NetworkRemote;
+
class Application : public QObject {
Q_OBJECT
@@ -107,6 +109,8 @@ class Application : public QObject {
SharedPtr lastfm_import() const;
+ SharedPtr network_remote() const;
+
void Exit();
QThread *MoveToNewThread(QObject *object);
diff --git a/src/networkremote/CMakeLists.txt b/src/networkremote/CMakeLists.txt
new file mode 100644
index 00000000..700fa222
--- /dev/null
+++ b/src/networkremote/CMakeLists.txt
@@ -0,0 +1,32 @@
+cmake_minimum_required(VERSION 3.14)
+
+set(SOURCES RemoteMessages.proto)
+
+link_directories(
+ ${GLIB_LIBRARY_DIRS}
+ ${PROTOBUF_LIBRARY_DIRS}
+)
+
+add_library(lib-networkremote OBJECT ${PROTO_SOURCES} ${SOURCES})
+
+target_include_directories(lib-networkremote SYSTEM PRIVATE
+ ${GLIB_INCLUDE_DIRS}
+ ${PROTOBUF_INCLUDE_DIRS}
+)
+
+target_include_directories(lib-networkremote PRIVATE
+ ${CMAKE_CURRENT_SOURCE_DIR}
+ ${CMAKE_CURRENT_BINARY_DIR}
+ ${CMAKE_SOURCE_DIR}/src
+ ${CMAKE_BINARY_DIR}/src
+)
+
+target_link_libraries(lib-networkremote PRIVATE
+ ${GLIB_LIBRARIES}
+ ${Protobuf_LIBRARIES}
+ Qt${QT_VERSION_MAJOR}::Core
+ Qt${QT_VERSION_MAJOR}::Network
+ Qt${QT_VERSION_MAJOR}::Gui
+)
+
+protobuf_generate(TARGET lib-networkremote)
diff --git a/src/networkremote/RemoteMessages.proto b/src/networkremote/RemoteMessages.proto
new file mode 100644
index 00000000..6b753fdb
--- /dev/null
+++ b/src/networkremote/RemoteMessages.proto
@@ -0,0 +1,131 @@
+syntax = "proto3";
+
+option optimize_for = LITE_RUNTIME;
+
+package nw.remote;
+
+enum MsgType {
+ MSG_TYPE_UNSPECIFIED = 0;
+
+ // Client message
+ MSG_TYPE_REQUEST_SONG_INFO = 1;
+ MSG_TYPE_REQUEST_PLAY = 2;
+ MSG_TYPE_REQUEST_NEXT = 3;
+ MSG_TYPE_REQUEST_PREVIOUS = 4;
+ MSG_TYPE_REQUEST_PAUSE = 5;
+ MSG_TYPE_REQUEST_STOP = 6;
+ MSG_TYPE_REQUEST_FINISH = 7;
+
+
+ // Server messages
+ MSG_TYPE_REPLY_SONG_INFO = 8;
+ MSG_TYPE_REPLY_PLAY = 9;
+ MSG_TYPE_REPLY_NEXT = 10;
+ MSG_TYPE_REPLY_PREVIOUS = 11;
+ MSG_TYPE_REPLY_PAUSE = 12;
+ MSG_TYPE_REPLY_STOP = 13;
+ MSG_TYPE_REPLY_FINISH = 14;
+ MSG_TYPE_ENGINE_STATE_CHANGE = 15;
+
+ // Bidirectional messages
+ MSG_TYPE_DISCONNECT = 16;
+}
+
+enum PlayerState{
+ PLAYER_STATUS_UNSPECIFIED = 0;
+ PLAYER_STATUS_PLAYING = 1;
+}
+
+enum EngineState {
+ ENGINE_STATE_EMPTY = 0;
+ ENGINE_STATE_IDELE = 1;
+ ENGINE_STATE_PLAYING = 2;
+ ENGINE_STATE_PAUSED = 3;
+}
+
+enum ReasonDisconnect {
+ REASON_DISCONNECT_SERVER_SHUTDOWN = 0;
+ REASON_DISCONNECT_CLIENT_SHUTDOWN = 1;
+}
+
+message RequestDisconnect {
+ ReasonDisconnect reason_disconnect = 1;
+}
+
+message SongMetadata{
+ uint32 id = 1;
+ string title = 2;
+ string album = 3;
+ string artist = 4;
+ string albumartist = 5;
+ uint32 track = 6;
+ string stryear = 7;
+ string genre = 8;
+ uint32 playcount = 9;
+ string songlength = 10;
+}
+message RequestSongMetadata {
+ bool send = 1;
+}
+
+message ResponseSongMetadata {
+ SongMetadata song_metadata = 1;
+ PlayerState player_state = 2;
+}
+
+message RequestNextTrack {
+ bool next = 1;
+}
+
+message ResponseNextTrack {
+ bool next = 1;
+}
+
+message RequestPreviousTrack {
+ bool previous = 1;
+}
+
+message ResponsePreviousTrack {
+ bool previous = 1;
+}
+
+message RequestPlay {
+ bool play = 1;
+}
+
+message ResponsePlay {
+ bool play = 1;
+}
+
+message RequestPause {
+ bool pause = 1;
+}
+
+message ResponsePause {
+ bool pause = 1;
+}
+
+message RequestStop {
+ bool stop = 1;
+}
+
+message EngineStateChange {
+ EngineState state = 1;
+}
+message Message {
+ MsgType type = 1;
+ RequestSongMetadata request_song_metadata = 2;
+ ResponseSongMetadata response_song_metadata = 3;
+ RequestNextTrack request_next_track = 4;
+ RequestPreviousTrack request_previous_track = 5;
+ RequestPlay request_play = 6;
+ RequestPause request_pause = 7;
+ RequestStop request_stop = 8;
+ EngineStateChange engine_state_change = 9;
+ RequestDisconnect request_disconnect = 10;
+ ResponseNextTrack response_next_track = 11;
+ ResponsePreviousTrack response_previous_track = 12;
+ ResponsePlay response_play = 13;
+ ResponsePause response_pause = 14;
+}
+
diff --git a/src/networkremote/client.cpp b/src/networkremote/client.cpp
new file mode 100644
index 00000000..214c5c8c
--- /dev/null
+++ b/src/networkremote/client.cpp
@@ -0,0 +1,68 @@
+#include "client.h"
+
+Client::Client(Application *app, QObject *parent)
+ : QObject{parent},
+ app_(app),
+ incomingMsg_(new IncomingMsg(app)),
+ outgoingMsg_(new OutgoingMsg(app)),
+ player_(app_->player())
+{
+}
+
+Client::~Client()
+{
+ incomingMsg_->deleteLater();
+ outgoingMsg_->deleteLater();
+}
+
+void Client::Init(QTcpSocket *socket)
+{
+ socket_ = socket;
+ QObject::connect(incomingMsg_,&IncomingMsg::InMsgParsed,this, &Client::ProcessIncoming);
+
+ incomingMsg_->Init(socket_);
+ outgoingMsg_->Init(socket_, player_);
+}
+
+QTcpSocket* Client::GetSocket()
+{
+ return socket_;
+}
+
+void Client::ProcessIncoming()
+{
+ msgType_ = incomingMsg_->GetMsgType();
+ switch (msgType_)
+ {
+ case nw::remote::MSG_TYPE_REQUEST_SONG_INFO:
+ outgoingMsg_->SendCurrentTrackInfo();
+ break;
+ case nw::remote::MSG_TYPE_REQUEST_PLAY:
+ player_->Play();
+ // In case the player was paused when the client started send the song info again
+ outgoingMsg_->SendCurrentTrackInfo();
+ break;
+ case nw::remote::MSG_TYPE_REQUEST_NEXT:
+ player_->Next();
+ outgoingMsg_->SendCurrentTrackInfo();
+ break;
+ case nw::remote::MSG_TYPE_REQUEST_PREVIOUS:
+ player_->Previous();
+ outgoingMsg_->SendCurrentTrackInfo();
+ break;
+ case nw::remote::MSG_TYPE_REQUEST_PAUSE:
+ player_->Pause();
+ break;
+ case nw::remote::MSG_TYPE_REQUEST_STOP:
+ break;
+ case nw::remote::MSG_TYPE_REQUEST_FINISH:
+ emit ClientIsLeaving();
+ break;
+ case nw::remote::MSG_TYPE_DISCONNECT:
+ break;
+ default:
+ qInfo("Unknown mwessage type");
+ break;
+ }
+}
+
diff --git a/src/networkremote/client.h b/src/networkremote/client.h
new file mode 100644
index 00000000..3107f945
--- /dev/null
+++ b/src/networkremote/client.h
@@ -0,0 +1,38 @@
+#ifndef CLIENT_H
+#define CLIENT_H
+
+#include
+#include
+
+#include "incomingmsg.h"
+#include "outgoingmsg.h"
+#include "core/player.h"
+
+
+class Application;
+
+class Client : public QObject
+{
+ Q_OBJECT
+public:
+ explicit Client(Application *app, QObject *parent = nullptr);
+ ~Client();
+ void Init(QTcpSocket*);
+ QTcpSocket* GetSocket();
+ void ProcessIncoming();
+
+signals:
+ void ReceiveMsg();
+ void PrepareResponse();
+ void ClientIsLeaving();
+
+private:
+ Application *app_;
+ QTcpSocket *socket_;
+ IncomingMsg *incomingMsg_;
+ OutgoingMsg *outgoingMsg_;
+ qint32 msgType_;
+ SharedPtr player_;
+};
+
+#endif // CLIENT_H
diff --git a/src/networkremote/clientmanager.cpp b/src/networkremote/clientmanager.cpp
new file mode 100644
index 00000000..ce253b2c
--- /dev/null
+++ b/src/networkremote/clientmanager.cpp
@@ -0,0 +1,74 @@
+#include "clientmanager.h"
+#include "core/application.h"
+#include "core/logging.h"
+
+
+ClientManager::ClientManager(Application *app, QObject *parent)
+ : QObject{parent},
+ app_(app)
+{
+ clients_ = new QVector;
+}
+
+ClientManager::~ClientManager()
+{}
+
+void ClientManager::AddClient(QTcpSocket *socket)
+{
+ qLog(Debug) << "New Client connection +++++++++++++++";
+ socket_ = socket;
+ QObject::connect(socket_, &QAbstractSocket::errorOccurred, this, &ClientManager::Error);
+ QObject::connect(socket_, &QAbstractSocket::stateChanged, this, &ClientManager::StateChanged);
+
+ client_ = new Client(app_);
+ client_->Init(socket_);
+ clients_->append(client_);
+ QObject::connect(client_, &Client::ClientIsLeaving, this, &ClientManager::RemoveClient);
+
+ qLog(Debug) << "Socket State is " << socket_->state();;
+ qLog(Debug) << "There are now +++++++++++++++" << clients_->count() << "clients connected";
+}
+
+void ClientManager::RemoveClient()
+{
+ for (Client* client : *clients_) {
+ if (client->GetSocket() == socket_){
+ clients_->removeAt(clients_->indexOf(client));
+ client->deleteLater();
+ }
+ }
+ socket_->close();
+
+ qLog(Debug) << "There are now +++++++++++++++" << clients_->count() << "clients connected";
+}
+
+void ClientManager::Ready()
+{
+ qLog(Debug) << "Socket Ready";
+}
+
+void ClientManager::Error(QAbstractSocket::SocketError socketError)
+{
+ switch (socketError) {
+ case QAbstractSocket::RemoteHostClosedError:
+ qLog(Debug) << "Remote Host closed";
+ break;
+ case QAbstractSocket::HostNotFoundError:
+ qLog(Debug) << "The host was not found. Please check the host name and port settings.";
+ break;
+ case QAbstractSocket::ConnectionRefusedError:
+ qLog(Debug) << "The connection was refused by the peer. ";
+ break;
+ default:
+ qLog(Debug) << "The following error occurred: %1." << socket_->errorString();
+ }
+}
+
+void ClientManager::StateChanged()
+{
+ qLog(Debug) << socket_->state();
+ qLog(Debug) << "State Changed";
+ if (socket_->state() == QAbstractSocket::UnconnectedState){
+ RemoveClient();
+ }
+}
diff --git a/src/networkremote/clientmanager.h b/src/networkremote/clientmanager.h
new file mode 100644
index 00000000..3dc66740
--- /dev/null
+++ b/src/networkremote/clientmanager.h
@@ -0,0 +1,32 @@
+#ifndef CLIENTMANAGER_H
+#define CLIENTMANAGER_H
+
+#include
+#include
+#include
+#include "networkremote/client.h"
+
+class Application;
+
+class ClientManager : public QObject
+{
+ Q_OBJECT
+public:
+ explicit ClientManager(Application *app, QObject *parent = nullptr);
+ ~ClientManager();
+ void AddClient(QTcpSocket *socket);
+ void RemoveClient();
+
+private slots:
+ void Ready();
+ void Error(QAbstractSocket::SocketError);
+ void StateChanged();
+
+private:
+ Application *app_;
+ QVector *clients_;
+ Client *client_ = nullptr;
+ QTcpSocket *socket_ = nullptr;
+};
+
+#endif // CLIENTMANAGER_H
diff --git a/src/networkremote/incomingmsg.cpp b/src/networkremote/incomingmsg.cpp
new file mode 100644
index 00000000..71418327
--- /dev/null
+++ b/src/networkremote/incomingmsg.cpp
@@ -0,0 +1,35 @@
+#include "incomingmsg.h"
+#include "core/logging.h"
+#include "core/player.h"
+
+IncomingMsg::IncomingMsg(Application *app, QObject *parent)
+ : QObject{parent},
+ msg_(new nw::remote::Message),
+ app_(app)
+{
+}
+
+void IncomingMsg::Init(QTcpSocket *socket)
+{
+ socket_ = socket;
+ QObject::connect(socket_, &QIODevice::readyRead, this, &IncomingMsg::ReadyRead);
+}
+
+void IncomingMsg::SetMsgType()
+{
+ msgString_ = msgStream_.toStdString();
+ msg_->ParseFromString(msgString_);
+ emit InMsgParsed();
+}
+
+qint32 IncomingMsg::GetMsgType()
+{
+ return msg_->type();
+}
+
+void IncomingMsg::ReadyRead()
+{
+ qLog(Debug) << "Ready To Read";
+ msgStream_ = socket_->readAll();
+ if (msgStream_.length() > 0) SetMsgType();
+}
diff --git a/src/networkremote/incomingmsg.h b/src/networkremote/incomingmsg.h
new file mode 100644
index 00000000..04121d6c
--- /dev/null
+++ b/src/networkremote/incomingmsg.h
@@ -0,0 +1,34 @@
+#ifndef INCOMINGMSG_H
+#define INCOMINGMSG_H
+
+#include
+#include
+#include "networkremote/RemoteMessages.pb.h"
+#include "core/application.h"
+
+class IncomingMsg : public QObject
+{
+ Q_OBJECT
+public:
+ explicit IncomingMsg(Application *app, QObject *parent = nullptr);
+ void Init(QTcpSocket*);
+ void SetMsgType();
+ qint32 GetMsgType();
+
+private slots:
+ void ReadyRead();
+
+signals:
+ void InMsgParsed();
+
+private:
+ nw::remote::Message *msg_;
+ QTcpSocket *socket_;
+ long bytesIn_;
+ QByteArray msgStream_;
+ std::string msgString_;
+ Application *app_;
+ qint32 msgType_;
+};
+
+#endif // INCOMINGMSG_H
diff --git a/src/networkremote/networkremote.cpp b/src/networkremote/networkremote.cpp
new file mode 100644
index 00000000..0c0d937a
--- /dev/null
+++ b/src/networkremote/networkremote.cpp
@@ -0,0 +1,84 @@
+#include
+
+#include "networkremote/networkremote.h"
+#include "core/application.h"
+#include "core/logging.h"
+#include "core/player.h"
+
+
+NetworkRemote* NetworkRemote::sInstance = nullptr;
+const char *NetworkRemote::kSettingsGroup = "Remote";
+
+NetworkRemote::NetworkRemote(Application* app, QObject *parent)
+ : QObject(parent),
+ app_(app),
+ original_thread_(nullptr)
+{
+ setObjectName("Strawberry Remote");
+ original_thread_ = thread();
+ sInstance = this;
+ server_ = new TcpServer(app_);
+}
+
+NetworkRemote::~NetworkRemote()
+{
+ stopTcpServer();
+}
+
+void NetworkRemote::Init()
+{
+ LoadSettings();
+ if (use_remote_){
+ startTcpServer();
+ }
+ else {
+ stopTcpServer();
+ }
+ qLog(Debug) << "NetworkRemote Init() ";
+}
+
+void NetworkRemote::Update()
+{
+ LoadSettings();
+ if (use_remote_){
+ stopTcpServer();
+ startTcpServer();
+ }
+ else {
+ stopTcpServer();
+ }
+ qLog(Debug) << "NetworkRemote Updated ==== ";
+}
+
+void NetworkRemote::LoadSettings()
+{
+ s_->Load();
+ use_remote_ = s_->UserRemote();
+ local_only_ = s_->LocalOnly();
+ remote_port_ = s_->GetPort();
+ ipAddr_.setAddress(s_->GetIpAddress());
+}
+
+void NetworkRemote::startTcpServer()
+{
+ server_->StartServer(ipAddr_,remote_port_);
+}
+
+void NetworkRemote::stopTcpServer()
+{
+ if (server_->ServerUp()){
+ qLog(Debug) << "TcpServer stopped ";
+ server_->StopServer();
+ }
+}
+
+NetworkRemote* NetworkRemote::Instance() {
+ if (!sInstance) {
+ // Error
+ return nullptr;
+ }
+
+ qLog(Debug) << "NetworkRemote instance is up ";
+ return sInstance;
+}
+
diff --git a/src/networkremote/networkremote.h b/src/networkremote/networkremote.h
new file mode 100644
index 00000000..1f86152a
--- /dev/null
+++ b/src/networkremote/networkremote.h
@@ -0,0 +1,45 @@
+#ifndef NETWORKREMOTE_H
+#define NETWORKREMOTE_H
+
+#include
+#include
+#include
+#include
+#include
+
+#include "tcpserver.h"
+#include "networkremote/remotesettings.h"
+
+class Application;
+class QThread;
+
+class NetworkRemote : public QObject
+{
+ Q_OBJECT
+public:
+ static const char* kSettingsGroup;
+ explicit NetworkRemote(Application* app, QObject *parent = nullptr);
+ static NetworkRemote* Instance();
+ ~NetworkRemote() override;
+
+public slots:
+ void Init();
+ void Update();
+ void LoadSettings();
+ void startTcpServer();
+ void stopTcpServer();
+
+private:
+ Application *app_;
+ bool use_remote_;
+ bool local_only_;
+ int remote_port_;
+ QHostAddress ipAddr_;
+ TcpServer *server_;
+ QThread *original_thread_;
+ static NetworkRemote* sInstance;
+ RemoteSettings *s_ = new RemoteSettings;
+
+};
+
+#endif // NETWORKREMOTE_H
diff --git a/src/networkremote/outgoingmsg.cpp b/src/networkremote/outgoingmsg.cpp
new file mode 100644
index 00000000..289f4faa
--- /dev/null
+++ b/src/networkremote/outgoingmsg.cpp
@@ -0,0 +1,85 @@
+#include "outgoingmsg.h"
+#include "core/player.h"
+#include "playlist/playlistmanager.h"
+
+OutgoingMsg::OutgoingMsg(Application *app, QObject *parent)
+ : QObject{parent},
+ app_(app),
+ msg_(new nw::remote::Message),
+ responeSong_(new nw::remote::ResponseSongMetadata)
+{
+}
+
+OutgoingMsg::~OutgoingMsg()
+{
+}
+
+void OutgoingMsg::Init(QTcpSocket *socket, SharedPtr player)
+{
+ socket_ = socket;
+ player_ = player;
+}
+
+
+void OutgoingMsg::SendCurrentTrackInfo()
+{
+ msg_->Clear();
+ song_ = new nw::remote::SongMetadata;
+ responeSong_->Clear();
+
+ //PlaylistItemPtr current_item() const;
+ currentItem_ = app_->playlist_manager()->active()->current_item();
+
+ //PlaylistItemPtr prt = player_->GetCurrentItem();
+
+
+ //if (playerState_ == EngineBase::State::Playing){
+ if (currentItem_ != NULL){
+ song_->mutable_title()->assign(currentItem_->Metadata().PrettyTitle().toStdString());
+ song_->mutable_album()->assign(currentItem_->Metadata().album().toStdString());
+ song_->mutable_artist()->assign(currentItem_->Metadata().artist().toStdString());
+ song_->mutable_albumartist()->assign(currentItem_->Metadata().albumartist().toStdString());
+ song_->set_track(currentItem_->Metadata().track());
+ song_->mutable_stryear()->assign(currentItem_->Metadata().PrettyYear().toStdString());
+ song_->mutable_genre()->assign(currentItem_->Metadata().genre().toStdString());
+ song_->set_playcount(currentItem_->Metadata().playcount());
+ song_->mutable_songlength()->assign(currentItem_->Metadata().PrettyLength().toStdString());
+
+ msg_->set_type(nw::remote::MSG_TYPE_REPLY_SONG_INFO);
+ msg_->mutable_response_song_metadata()->set_player_state(nw::remote::PLAYER_STATUS_PLAYING);
+ msg_->mutable_response_song_metadata()->set_allocated_song_metadata(song_);
+ }
+ else {
+ /* NOTE: TODO
+ * I couldn't figure out how to get the song data if the song wasn't playing
+ *
+ * */
+ msg_->set_type(nw::remote::MSG_TYPE_UNSPECIFIED);
+ msg_->mutable_response_song_metadata()->set_player_state(nw::remote::PLAYER_STATUS_UNSPECIFIED);
+ }
+ SendMsg();
+}
+
+void OutgoingMsg::SendMsg()
+{
+ std::string msgOut;
+
+ msg_->SerializeToString(&msgOut);
+
+
+ bytesOut_ = msg_->ByteSizeLong();
+
+ if(socket_->isWritable())
+ {
+
+ socket_->write(QByteArray::fromStdString(msgOut));
+ qInfo() << socket_->bytesToWrite() << " bytes written to socket " << socket_->socketDescriptor();
+ statusOk_ = true;
+ msg_->Clear();
+ }
+ else
+ {
+ statusOk_ = false;
+ }
+}
+
diff --git a/src/networkremote/outgoingmsg.h b/src/networkremote/outgoingmsg.h
new file mode 100644
index 00000000..1a343f42
--- /dev/null
+++ b/src/networkremote/outgoingmsg.h
@@ -0,0 +1,38 @@
+#ifndef OUTGOINGMSG_H
+#define OUTGOINGMSG_H
+
+#include
+#include "core/application.h"
+#include "playlist/playlist.h"
+#include "playlist/playlistitem.h"
+#include "qtcpsocket.h"
+#include "networkremote/RemoteMessages.pb.h"
+
+class OutgoingMsg : public QObject
+{
+ Q_OBJECT
+public:
+ explicit OutgoingMsg(Application *app, QObject *parent = nullptr);
+ ~OutgoingMsg();
+ void Init(QTcpSocket*, SharedPtr);
+ void SendCurrentTrackInfo();
+ void SendMsg();
+
+private:
+ Application *app_;
+ PlaylistItemPtr currentItem_;
+ Playlist *playlist_;
+ QTcpSocket *socket_;
+ qint32 msgType_;
+ QByteArray msgStream_;
+ nw::remote::Message *msg_;
+ long bytesOut_;
+ std::string msgString_;
+ nw::remote::SongMetadata *song_;
+ nw::remote::ResponseSongMetadata *responeSong_;
+ SharedPtr player_ ;
+ bool statusOk_;
+
+};
+
+#endif // OUTGOINGMSG_H
diff --git a/src/networkremote/remotesettings.cpp b/src/networkremote/remotesettings.cpp
new file mode 100644
index 00000000..ad5c3ae4
--- /dev/null
+++ b/src/networkremote/remotesettings.cpp
@@ -0,0 +1,102 @@
+#include
+#include
+
+#include "remotesettings.h"
+#include "core/logging.h"
+
+const char *RemoteSettings::kSettingsGroup = "NetworkRemote";
+
+RemoteSettings::RemoteSettings(QObject *parent)
+ : QObject{parent}
+{}
+
+RemoteSettings::~RemoteSettings()
+{}
+
+void RemoteSettings::Load()
+{
+ SetIpAdress();
+ s_.beginGroup(RemoteSettings::kSettingsGroup);
+ if (!s_.contains("useRemote")){
+ qLog(Debug) << "First time run the Network Remote";
+ s_.setValue("useRemote", false);
+ s_.setValue("localOnly",false);
+ s_.setValue("remotePort",5050);
+ s_.setValue("ipAddress",ipAddr_);
+ }
+ else {
+ use_remote_ = s_.value("useRemote").toBool();
+ local_only_ = s_.value("localOnly").toBool();
+ remote_port_ = s_.value("remotePort").toInt();
+ s_.setValue("ipAddress",ipAddr_);
+ }
+ s_.endGroup();
+ qInfo("QSettings Loaded ++++++++++++++++");
+}
+
+void RemoteSettings::Save()
+{
+ s_.beginGroup(RemoteSettings::kSettingsGroup);
+ s_.setValue("useRemote",use_remote_);
+ s_.setValue("localOnly",local_only_);
+ s_.setValue("remotePort",remote_port_);
+ s_.setValue("ipAddress",ipAddr_);
+ s_.endGroup();
+ s_.sync();
+ qInfo("Saving QSettings ++++++++++++++++");
+}
+
+bool RemoteSettings::UserRemote()
+{
+ return use_remote_;
+}
+
+bool RemoteSettings::LocalOnly()
+{
+ return local_only_;
+}
+
+QString RemoteSettings::GetIpAddress()
+{
+ return ipAddr_;
+}
+
+int RemoteSettings::GetPort()
+{
+ return remote_port_;
+}
+
+void RemoteSettings::SetUseRemote(bool useRemote)
+{
+ use_remote_ = useRemote;
+ Save();
+}
+
+void RemoteSettings::SetLocalOnly(bool localOnly)
+{
+ local_only_ = localOnly;
+ Save();
+}
+
+void RemoteSettings::SetIpAdress()
+{
+ bool found = false;
+ QList hostList = QNetworkInterface::allAddresses();
+
+ for (const QHostAddress &address : hostList)
+ {
+ if (address.protocol() == QAbstractSocket::IPv4Protocol && address.isLoopback() == false && !found){
+ // NOTE: this code currently only takes the first ip address it finds
+ // +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
+ qInfo("Warning: The code only picks the first IPv4 address");
+ found = true;
+ ipAddr_ = address.toString();
+ }
+ }
+}
+
+void RemoteSettings::SetPort(int port)
+{
+ remote_port_ = port;
+ Save();
+}
diff --git a/src/networkremote/remotesettings.h b/src/networkremote/remotesettings.h
new file mode 100644
index 00000000..bee4287a
--- /dev/null
+++ b/src/networkremote/remotesettings.h
@@ -0,0 +1,33 @@
+#ifndef REMOTESETTINGS_H
+#define REMOTESETTINGS_H
+
+#include
+#include
+
+class RemoteSettings : public QObject
+{
+ Q_OBJECT
+public:
+ static const char *kSettingsGroup;
+ explicit RemoteSettings(QObject *parent = nullptr);
+ ~RemoteSettings();
+ void Load();
+ void Save();
+ bool UserRemote();
+ bool LocalOnly();
+ QString GetIpAddress();
+ int GetPort();
+ void SetUseRemote(bool);
+ void SetLocalOnly(bool);
+ void SetIpAdress ();
+ void SetPort(int);
+
+private:
+ QSettings s_;
+ bool use_remote_ = false;
+ bool local_only_ = false;
+ int remote_port_ = 5050;
+ QString ipAddr_ = "0.0.0.0";
+};
+
+#endif // REMOTESETTINGS_H
diff --git a/src/networkremote/tcpserver.cpp b/src/networkremote/tcpserver.cpp
new file mode 100644
index 00000000..37e0b840
--- /dev/null
+++ b/src/networkremote/tcpserver.cpp
@@ -0,0 +1,48 @@
+#include "tcpserver.h"
+#include "core/logging.h"
+#include "networkremote/clientmanager.h"
+#include
+
+
+TcpServer::TcpServer(Application* app, QObject *parent)
+ : QObject{parent},
+ app_(app)
+{
+ server_ = new QTcpServer(this);
+ clientMgr_ = new ClientManager(app_);
+ connect(server_,&QTcpServer::newConnection, this, &TcpServer::NewTcpConnection);
+}
+
+TcpServer::~TcpServer()
+{
+}
+
+void TcpServer::StartServer(QHostAddress ipAddr, int port)
+{
+ bool ok = false;
+ server_->setProxy(QNetworkProxy::NoProxy);
+ ok = server_->listen(ipAddr, port);
+ if (ok){
+ qLog(Debug) << "TCP Server Started on --- " << ipAddr.toString() << " and port -- " << port;
+ }
+}
+
+void TcpServer::NewTcpConnection()
+{
+ socket_ = server_->nextPendingConnection();
+ clientMgr_->AddClient(socket_);
+ qLog(Debug) << "New Socket -------------------";
+}
+
+void TcpServer::StopServer()
+{
+ server_->close();
+ qLog(Debug) << "TCP Server Stopped ----------------------";
+}
+
+
+bool TcpServer::ServerUp()
+{
+ return server_->isListening();
+}
+
diff --git a/src/networkremote/tcpserver.h b/src/networkremote/tcpserver.h
new file mode 100644
index 00000000..2bc8c96a
--- /dev/null
+++ b/src/networkremote/tcpserver.h
@@ -0,0 +1,38 @@
+#ifndef TCPSERVER_H
+#define TCPSERVER_H
+
+#include
+#include
+#include
+#include
+#include
+#include "networkremote/clientmanager.h"
+
+class Application;
+
+class TcpServer : public QObject
+{
+ Q_OBJECT
+public:
+ static const char *kSettingsGroup;
+
+ explicit TcpServer(Application* app, QObject *parent = nullptr);
+ ~TcpServer();
+
+ bool ServerUp();
+
+public slots:
+ void NewTcpConnection();
+ void StartServer(QHostAddress ipAddr, int port);
+ void StopServer();
+
+signals:
+
+private:
+ Application *app_;
+ QTcpServer *server_;
+ QTcpSocket *socket_;
+ ClientManager *clientMgr_;
+};
+
+#endif // TCPSERVER_H
diff --git a/src/settings/networkremotesettingspage.cpp b/src/settings/networkremotesettingspage.cpp
new file mode 100644
index 00000000..5300ca9c
--- /dev/null
+++ b/src/settings/networkremotesettingspage.cpp
@@ -0,0 +1,98 @@
+#include
+#include
+#include
+
+#include "core/iconloader.h"
+#include "networkremote/networkremote.h"
+#include "settings/settingsdialog.h"
+#include "settings/networkremotesettingspage.h"
+#include "ui_networkremotesettingspage.h"
+
+NetworkRemoteSettingsPage::NetworkRemoteSettingsPage(SettingsDialog *dialog, QWidget *parent) :
+ SettingsPage(dialog,parent),
+ ui_(new Ui_NetworkRemoteSettingsPage)
+{
+
+ ui_->setupUi(this);
+ setWindowIcon(IconLoader::Load("network-remote", true, 0,32));
+ QObject::connect(ui_->useRemoteClient,&QAbstractButton::clicked, this, &NetworkRemoteSettingsPage::RemoteButtonClicked);
+ QObject::connect(ui_->localConnectionsOnly, &QAbstractButton::clicked, this, &NetworkRemoteSettingsPage::LocalConnectButtonClicked);
+ QObject::connect(ui_->portSelected, &QAbstractSpinBox::editingFinished, this, &NetworkRemoteSettingsPage::PortChanged);
+}
+
+NetworkRemoteSettingsPage::~NetworkRemoteSettingsPage()
+{
+ delete ui_;
+}
+
+void NetworkRemoteSettingsPage::Load()
+{
+ ui_->portSelected->setRange(5050, 65535);
+ ui_->ip_address->setText("0.0.0.0");
+ s_->Load();
+
+ ui_->useRemoteClient->setCheckable(true);
+ ui_->useRemoteClient->setChecked(s_->UserRemote());
+ if (s_->UserRemote()){
+ ui_->localConnectionsOnly->setCheckable(true);
+ ui_->localConnectionsOnly->setChecked(s_->LocalOnly());
+ ui_->portSelected->setReadOnly(false);
+ ui_->portSelected->setValue(s_->GetPort());
+ }
+ else{
+ ui_->localConnectionsOnly->setCheckable(false);
+ ui_->portSelected->setReadOnly(true);
+ }
+
+ DisplayIP();
+ qInfo("SettingsPage Loaded QSettings ++++++++++++++++");
+
+ Init(ui_->layout_networkremotesettingspage->parentWidget());
+}
+
+void NetworkRemoteSettingsPage::Save()
+{
+ qInfo("Saving QSettings ++++++++++++++++");
+}
+
+void NetworkRemoteSettingsPage::Refresh()
+{
+ if (NetworkRemote::Instance()) {
+ qInfo() << "NetworkRemote Instance is up";
+ NetworkRemote::Instance()->Update();
+ }
+}
+
+void NetworkRemoteSettingsPage::DisplayIP()
+{
+ ui_->ip_address->setText(s_->GetIpAddress());
+}
+
+void NetworkRemoteSettingsPage::RemoteButtonClicked()
+{
+ s_->SetUseRemote(ui_->useRemoteClient->isChecked());
+ ui_->useRemoteClient->setChecked(s_->UserRemote());
+ if (ui_->useRemoteClient->isChecked()){
+ ui_->localConnectionsOnly->setCheckable(true);
+ ui_->portSelected->setReadOnly(false);
+ }
+ else{
+ ui_->localConnectionsOnly->setCheckable(false);
+ ui_->portSelected->setReadOnly(true);
+ }
+ Refresh();
+}
+
+
+void NetworkRemoteSettingsPage::LocalConnectButtonClicked()
+{
+ s_->SetLocalOnly(ui_->localConnectionsOnly->isChecked());
+ Refresh();
+}
+
+void NetworkRemoteSettingsPage::PortChanged()
+{
+ s_->SetPort(ui_->portSelected->value());
+ Refresh();
+}
+
diff --git a/src/settings/networkremotesettingspage.h b/src/settings/networkremotesettingspage.h
new file mode 100644
index 00000000..c6226461
--- /dev/null
+++ b/src/settings/networkremotesettingspage.h
@@ -0,0 +1,39 @@
+#ifndef NETWORKREMOTESETTINGSPAGE_H
+#define NETWORKREMOTESETTINGSPAGE_H
+
+#include
+#include
+
+#include "settingspage.h"
+#include "networkremote/remotesettings.h"
+
+class SettingsDialog;
+class Ui_NetworkRemoteSettingsPage;
+class NetworkRemote;
+
+class NetworkRemoteSettingsPage : public SettingsPage
+{
+ Q_OBJECT
+
+public:
+ explicit NetworkRemoteSettingsPage(SettingsDialog *dialog, QWidget *parent = nullptr);
+ ~NetworkRemoteSettingsPage() override;
+ void Load() override;
+ void Save() override;
+ void Refresh();
+
+signals:
+ void remoteSettingsChanged();
+
+private:
+ Ui_NetworkRemoteSettingsPage *ui_;
+ RemoteSettings *s_ = new RemoteSettings;
+
+private slots:
+ void RemoteButtonClicked();
+ void LocalConnectButtonClicked();
+ void PortChanged();
+ void DisplayIP();
+};
+
+#endif // NETWORKREMOTESETTINGSPAGE_H
diff --git a/src/settings/networkremotesettingspage.ui b/src/settings/networkremotesettingspage.ui
new file mode 100644
index 00000000..8a2e3181
--- /dev/null
+++ b/src/settings/networkremotesettingspage.ui
@@ -0,0 +1,101 @@
+
+
+ NetworkRemoteSettingsPage
+
+
+
+ 0
+ 0
+ 496
+ 195
+
+
+
+ Remote
+
+
+ -
+
+
-
+
+
+ Use Remote Network Client
+
+
+
+ -
+
+
+
+
+
+
+
+ 20
+ 20
+ 101
+ 17
+
+
+
+ Remote Port
+
+
+
+
+
+ 160
+ 10
+ 71
+ 26
+
+
+
+
+
+
+ 20
+ 60
+ 231
+ 23
+
+
+
+ Only allow local connections
+
+
+
+
+
+ 30
+ 110
+ 141
+ 17
+
+
+
+ Your IP Address is
+
+
+
+
+
+ 240
+ 110
+ 191
+ 17
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/settings/settingsdialog.cpp b/src/settings/settingsdialog.cpp
index 98d75239..76785b4a 100644
--- a/src/settings/settingsdialog.cpp
+++ b/src/settings/settingsdialog.cpp
@@ -65,6 +65,7 @@
#include "lyricssettingspage.h"
#include "transcodersettingspage.h"
#include "networkproxysettingspage.h"
+#include "networkremotesettingspage.h"
#include "appearancesettingspage.h"
#include "contextsettingspage.h"
#include "notificationssettingspage.h"
@@ -141,6 +142,7 @@ SettingsDialog::SettingsDialog(Application *app, OSDBase *osd, QMainWindow *main
AddPage(Page::Transcoding, new TranscoderSettingsPage(this, this), general);
#endif
AddPage(Page::Proxy, new NetworkProxySettingsPage(this, this), general);
+ AddPage(Page::Remote, new NetworkRemoteSettingsPage(this, this), general);
QTreeWidgetItem *iface = AddCategory(tr("User interface"));
AddPage(Page::Appearance, new AppearanceSettingsPage(this, this), iface);
diff --git a/src/settings/settingsdialog.h b/src/settings/settingsdialog.h
index cbdb23e4..1d37fc86 100644
--- a/src/settings/settingsdialog.h
+++ b/src/settings/settingsdialog.h
@@ -92,6 +92,7 @@ class SettingsDialog : public QDialog {
Subsonic,
Tidal,
Qobuz,
+ Remote,
};
enum Role {