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 {