diff --git a/CMakeLists.txt b/CMakeLists.txt index 7097ccea4..d0fc439c1 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -423,6 +423,7 @@ add_subdirectory(tools/ultimate_lyrics_parser) add_subdirectory(ext/libclementine-common) add_subdirectory(ext/libclementine-tagreader) add_subdirectory(ext/clementine-tagreader) +add_subdirectory(ext/libclementine-remote) option(WITH_DEBIAN OFF) if(WITH_DEBIAN) diff --git a/ext/libclementine-remote/CMakeLists.txt b/ext/libclementine-remote/CMakeLists.txt new file mode 100644 index 000000000..f6fc7685a --- /dev/null +++ b/ext/libclementine-remote/CMakeLists.txt @@ -0,0 +1,16 @@ +include_directories(${PROTOBUF_INCLUDE_DIRS}) + +set(MESSAGES + remotecontrolmessages.proto +) + +protobuf_generate_cpp(PROTO_SOURCES PROTO_HEADERS ${MESSAGES}) + +add_library(libclementine-remote STATIC + ${PROTO_SOURCES} +) + +target_link_libraries(libclementine-remote + libclementine-common +) + diff --git a/ext/libclementine-remote/remotecontrolmessages.proto b/ext/libclementine-remote/remotecontrolmessages.proto new file mode 100644 index 000000000..5a700a3ee --- /dev/null +++ b/ext/libclementine-remote/remotecontrolmessages.proto @@ -0,0 +1,73 @@ +package pb.remote; + +enum MsgType { + // Messages generally send from client to server + CONNECT = 0; + DISCONNECT = 1; + REQUEST_PLAYLISTS = 2; + REQUEST_PLAYLIST_SONGS = 3; + CHANGE_SONG = 4; + SET_VOLUME = 5; + + // Messages send by both + PLAY = 20; + PLAYPAUSE = 21; + PAUSE = 22; + STOP = 23; + NEXT = 24; + PREV = 25; + + // Messages send from server to client + INFOS = 40; + CURRENT_METAINFOS = 41; + PLAYLISTS = 42; + PLAYLIST_SONGS = 43; + KEEP_ALIVE = 44; +} + +enum EngineState { + Empty = 0; + Idle = 1; + Playing = 2; + Paused = 3; +} + +message Message { + required MsgType msgType = 1; + + optional EngineState state = 2; + optional ClementineInfos infos = 3; + optional SongMetadata currentSong = 4; + optional int32 volume = 5; + repeated Playlist playlists = 6; +} + +message ClementineInfos { + optional string version = 1; +} + +message SongMetadata { + optional int32 id = 1; + optional int32 index = 2; + optional string title = 3; + optional string album = 4; + optional string artist = 5; + optional string albumartist = 6; + optional int32 track = 7; + optional int32 disc = 8; + optional string pretty_year = 9; + optional string genre = 10; + optional int32 playcount = 11; + optional string pretty_length = 12; + optional string art = 13; +} + +message Playlist { + optional int32 id = 1; + optional string name = 2; + optional int32 item_count = 3; + optional bool active = 4; + + repeated SongMetadata songs = 10; +} + diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 03c060094..8d35283b5 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -62,6 +62,8 @@ endif(HAVE_BREAKPAD) include_directories(${CMAKE_SOURCE_DIR}/ext/libclementine-common) include_directories(${CMAKE_SOURCE_DIR}/ext/libclementine-tagreader) include_directories(${CMAKE_BINARY_DIR}/ext/libclementine-tagreader) +include_directories(${CMAKE_SOURCE_DIR}/ext/libclementine-remote) +include_directories(${CMAKE_BINARY_DIR}/ext/libclementine-remote) cmake_policy(SET CMP0011 NEW) include(../cmake/ParseArguments.cmake) @@ -211,6 +213,11 @@ set(SOURCES musicbrainz/chromaprinter.cpp musicbrainz/musicbrainzclient.cpp musicbrainz/tagfetcher.cpp + + networkremote/networkremotehelper.cpp + networkremote/networkremote.cpp + networkremote/incomingdataparser.cpp + networkremote/outgoingdatacreator.cpp playlist/dynamicplaylistcontrols.cpp playlist/playlist.cpp @@ -323,6 +330,7 @@ set(SOURCES ui/iconloader.cpp ui/mainwindow.cpp ui/networkproxysettingspage.cpp + ui/networkremotesettingspage.cpp ui/notificationssettingspage.cpp ui/organisedialog.cpp ui/organiseerrordialog.cpp @@ -483,6 +491,11 @@ set(HEADERS musicbrainz/acoustidclient.h musicbrainz/musicbrainzclient.h musicbrainz/tagfetcher.h + + networkremote/networkremotehelper.h + networkremote/networkremote.h + networkremote/incomingdataparser.h + networkremote/outgoingdatacreator.h playlist/dynamicplaylistcontrols.h playlist/playlist.h @@ -580,6 +593,7 @@ set(HEADERS ui/globalshortcutssettingspage.h ui/mainwindow.h ui/networkproxysettingspage.h + ui/networkremotesettingspage.h ui/notificationssettingspage.h ui/organisedialog.h ui/organiseerrordialog.h @@ -697,6 +711,7 @@ set(UI ui/globalshortcutssettingspage.ui ui/mainwindow.ui ui/networkproxysettingspage.ui + ui/networkremotesettingspage.ui ui/notificationssettingspage.ui ui/organisedialog.ui ui/organiseerrordialog.ui @@ -1125,6 +1140,7 @@ add_dependencies(clementine_lib pot) target_link_libraries(clementine_lib libclementine-common libclementine-tagreader + libclementine-remote ${SHA2_LIBRARIES} ${TAGLIB_LIBRARIES} ${MYGPOQT_LIBRARIES} diff --git a/src/core/application.cpp b/src/core/application.cpp index 59ddeecae..d9ee0b5fd 100644 --- a/src/core/application.cpp +++ b/src/core/application.cpp @@ -30,6 +30,8 @@ #include "globalsearch/globalsearch.h" #include "library/library.h" #include "library/librarybackend.h" +#include "networkremote/networkremote.h" +#include "networkremote/networkremotehelper.h" #include "playlist/playlistbackend.h" #include "playlist/playlistmanager.h" #include "podcasts/gpoddersync.h" @@ -63,7 +65,9 @@ Application::Application(QObject* parent) podcast_downloader_(NULL), gpodder_sync_(NULL), moodbar_loader_(NULL), - moodbar_controller_(NULL) + moodbar_controller_(NULL), + network_remote_(NULL), + network_remote_helper_(NULL) { tag_reader_client_ = new TagReaderClient(this); MoveToNewThread(tag_reader_client_); @@ -100,6 +104,13 @@ Application::Application(QObject* parent) moodbar_controller_ = new MoodbarController(this, this); #endif + // Network Remote + network_remote_ = new NetworkRemote(this); + MoveToNewThread(network_remote_); + + network_remote_helper_ = new NetworkRemoteHelper(this); + network_remote_helper_->StartServer(); + library_->Init(); DoInAMinuteOrSo(database_, SLOT(DoBackup())); diff --git a/src/core/application.h b/src/core/application.h index 3ed02ebdb..8258183f3 100644 --- a/src/core/application.h +++ b/src/core/application.h @@ -36,6 +36,8 @@ class LibraryBackend; class LibraryModel; class MoodbarController; class MoodbarLoader; +class NetworkRemote; +class NetworkRemoteHelper; class Player; class PlaylistBackend; class PodcastDownloader; @@ -73,6 +75,8 @@ public: GPodderSync* gpodder_sync() const { return gpodder_sync_; } MoodbarLoader* moodbar_loader() const { return moodbar_loader_; } MoodbarController* moodbar_controller() const { return moodbar_controller_; } + NetworkRemote* network_remote() const { return network_remote_; } + NetworkRemoteHelper* network_remote_helper() const { return network_remote_helper_; } LibraryBackend* library_backend() const; LibraryModel* library_model() const; @@ -111,6 +115,8 @@ private: GPodderSync* gpodder_sync_; MoodbarLoader* moodbar_loader_; MoodbarController* moodbar_controller_; + NetworkRemote* network_remote_; + NetworkRemoteHelper* network_remote_helper_; QList objects_in_threads_; QList threads_; diff --git a/src/networkremote/incomingdataparser.cpp b/src/networkremote/incomingdataparser.cpp new file mode 100644 index 000000000..3bd41f121 --- /dev/null +++ b/src/networkremote/incomingdataparser.cpp @@ -0,0 +1,130 @@ +/* This file is part of Clementine. + Copyright 2012, Andreas Muttscheller + + Clementine 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. + + Clementine 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 Clementine. If not, see . +*/ + +#include "incomingdataparser.h" +#include "core/logging.h" +#include "engines/enginebase.h" +#include "playlist/playlistmanager.h" + +IncomingDataParser::IncomingDataParser(Application* app) + :app_(app) +{ + // Connect all the signals + // due the player is in a different thread, we cannot access these functions directly + connect(this, SIGNAL(Play()), + app_->player(), SLOT(Play())); + connect(this, SIGNAL(PlayPause()), + app_->player(), SLOT(PlayPause())); + connect(this, SIGNAL(Pause()), + app_->player(), SLOT(Pause())); + connect(this, SIGNAL(Stop()), + app_->player(), SLOT(Stop())); + connect(this, SIGNAL(Next()), + app_->player(), SLOT(Next())); + connect(this, SIGNAL(Previous()), + app_->player(), SLOT(Previous())); + connect(this, SIGNAL(SetVolume(int)), + app_->player(), SLOT(SetVolume(int))); + connect(this, SIGNAL(PlayAt(int,Engine::TrackChangeFlags,bool)), + app_->player(), SLOT(PlayAt(int,Engine::TrackChangeFlags,bool))); + connect(this, SIGNAL(SetActivePlaylist(int)), + app_->playlist_manager(), SLOT(SetActivePlaylist(int))); +} + +IncomingDataParser::~IncomingDataParser() { +} + +bool IncomingDataParser::close_connection() { + return close_connection_; +} + +void IncomingDataParser::Parse(const QByteArray& b64_data) { + close_connection_ = false; + QByteArray pb_data = QByteArray::fromBase64(b64_data); + + // Parse the incoming data + pb::remote::Message msg; + if (!msg.ParseFromArray(pb_data.constData(), pb_data.size())) { + qLog(Info) << "Couldn't parse data"; + return; + } + + // Now check what's to do + switch (msg.msgtype()) { + case pb::remote::CONNECT: emit SendClementineInfos(); + emit SendFirstData(); + break; + case pb::remote::DISCONNECT: close_connection_ = true; + break; + case pb::remote::REQUEST_PLAYLISTS: emit SendAllPlaylists(); + break; + case pb::remote::REQUEST_PLAYLIST_SONGS: GetPlaylistSongs(&msg); + break; + case pb::remote::SET_VOLUME: emit SetVolume(msg.volume()); + break; + case pb::remote::PLAY: emit Play(); + break; + case pb::remote::PLAYPAUSE: emit PlayPause(); + break; + case pb::remote::PAUSE: emit Pause(); + break; + case pb::remote::STOP: emit Stop(); + break; + case pb::remote::NEXT: emit Next(); + break; + case pb::remote::PREV: emit Previous(); + break; + case pb::remote::CHANGE_SONG: ChangeSong(&msg); + break; + default: break; + } +} + +void IncomingDataParser::GetPlaylistSongs(pb::remote::Message* msg) { + // Check if we got a playlist + if (msg->playlists_size() == 0) + { + return; + } + + // Get the first entry and send the songs + pb::remote::Playlist playlist = msg->playlists(0); + emit SendPlaylistSongs(playlist.id()); +} + +void IncomingDataParser::ChangeSong(pb::remote::Message* msg) { + // Check if we got a song + if (msg->playlists_size() == 0) { + return; + } + + // Get the first entry and check if there is a song + pb::remote::Playlist playlist = msg->playlists(0); + if (playlist.songs_size() == 0) { + return; + } + + pb::remote::SongMetadata song = playlist.songs(0); + + // Check if we need to change the playlist + if (playlist.id() != app_->playlist_manager()->active_id()) { + emit SetActivePlaylist(playlist.id()); + } + + // Play the selected song + emit PlayAt(song.index(), Engine::Manual, false); +} diff --git a/src/networkremote/incomingdataparser.h b/src/networkremote/incomingdataparser.h new file mode 100644 index 000000000..b9c80571c --- /dev/null +++ b/src/networkremote/incomingdataparser.h @@ -0,0 +1,41 @@ +#ifndef INCOMINGDATAPARSER_H +#define INCOMINGDATAPARSER_H + +#include "core/player.h" +#include "core/application.h" +#include "remotecontrolmessages.pb.h" + +class IncomingDataParser : public QObject { + Q_OBJECT +public: + IncomingDataParser(Application* app); + ~IncomingDataParser(); + + void Parse(const QByteArray& pb_data); + bool close_connection(); + +signals: + void SendClementineInfos(); + void SendFirstData(); + void SendAllPlaylists(); + void SendPlaylistSongs(int id); + + void Play(); + void PlayPause(); + void Pause(); + void Stop(); + void Next(); + void Previous(); + void SetVolume(int volume); + void PlayAt(int i, Engine::TrackChangeFlags change, bool reshuffle); + void SetActivePlaylist(int id); + +private: + Application* app_; + bool close_connection_; + + void GetPlaylistSongs(pb::remote::Message* msg); + void ChangeSong(pb::remote::Message* msg); +}; + +#endif // INCOMINGDATAPARSER_H diff --git a/src/networkremote/networkremote.cpp b/src/networkremote/networkremote.cpp new file mode 100644 index 000000000..abb20938c --- /dev/null +++ b/src/networkremote/networkremote.cpp @@ -0,0 +1,144 @@ +/* This file is part of Clementine. + Copyright 2012, Andreas Muttscheller + + Clementine 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. + + Clementine 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 Clementine. If not, see . +*/ + +#include "core/logging.h" +#include "covers/currentartloader.h" +#include "playlist/playlistmanager.h" + +#include "networkremote.h" + +#include +#include + +const char* NetworkRemote::kSettingsGroup = "NetworkRemote"; +const int NetworkRemote::kDefaultServerPort = 5500; + +NetworkRemote::NetworkRemote(Application* app) + : app_(app) +{ +} + + +NetworkRemote::~NetworkRemote() { + server_->close(); + delete incoming_data_parser_; + delete outgoing_data_creator_; +} + +void NetworkRemote::ReadSettings() { + QSettings s; + + s.beginGroup(NetworkRemote::kSettingsGroup); + use_remote_ = s.value("use_remote").toBool(); + port_ = s.value("port").toInt(); + if (port_ == 0) { + port_ = kDefaultServerPort; + } + s.endGroup(); +} + +void NetworkRemote::SetupServer() { + server_ = new QTcpServer(); + incoming_data_parser_ = new IncomingDataParser(app_); + outgoing_data_creator_ = new OutgoingDataCreator(app_); + + connect(app_->current_art_loader(), + SIGNAL(ArtLoaded(const Song&, const QString&, const QImage&)), + outgoing_data_creator_, + SLOT(CurrentSongChanged(const Song&, const QString&, const QImage&))); +} + +void NetworkRemote::StartServer() { + if (!app_) { + qLog(Error) << "Start Server called without having an application!"; + return; + } + // Check if user desires to start a network remote server + ReadSettings(); + if (!use_remote_) { + qLog(Info) << "Network Remote deactivated"; + return; + } + + qLog(Info) << "Starting network remote"; + + clients_ = NULL; + + connect(server_, SIGNAL(newConnection()), this, SLOT(AcceptConnection())); + + server_->listen(QHostAddress::Any, port_); + + qLog(Info) << "Listening on port " << port_; +} + +void NetworkRemote::StopServer() { + if (server_->isListening()) { + server_->close(); + } +} + +void NetworkRemote::ReloadSettings() { + StopServer(); + StartServer(); +} + +void NetworkRemote::AcceptConnection() { + if (!clients_) { + // Create a new QList with clients + clients_ = new QList(); + outgoing_data_creator_->SetClients(clients_); + + // Setting up the signals, but only once + connect(incoming_data_parser_, SIGNAL(SendClementineInfos()), + outgoing_data_creator_, SLOT(SendClementineInfos())); + connect(incoming_data_parser_, SIGNAL(SendFirstData()), + outgoing_data_creator_, SLOT(SendFirstData())); + connect(incoming_data_parser_, SIGNAL(SendAllPlaylists()), + outgoing_data_creator_, SLOT(SendAllPlaylists())); + connect(incoming_data_parser_, SIGNAL(SendPlaylistSongs(int)), + outgoing_data_creator_, SLOT(SendPlaylistSongs(int))); + + connect(app_->playlist_manager(), SIGNAL(ActiveChanged(Playlist*)), + outgoing_data_creator_, SLOT(ActiveChanged(Playlist*))); + connect(app_->playlist_manager(), SIGNAL(PlaylistChanged(Playlist*)), + outgoing_data_creator_, SLOT(PlaylistChanged(Playlist*))); + + connect(app_->player(), SIGNAL(VolumeChanged(int)), outgoing_data_creator_, + SLOT(VolumeChanged(int))); + connect(app_->player()->engine(), SIGNAL(StateChanged(Engine::State)), + outgoing_data_creator_, SLOT(StateChanged(Engine::State))); + } + QTcpSocket* client = server_->nextPendingConnection(); + + clients_->push_back(client); + + // Connect to the slot IncomingData when receiving data + connect(client, SIGNAL(readyRead()), this, SLOT(IncomingData())); +} + +void NetworkRemote::IncomingData() { + QTcpSocket* client = static_cast(QObject::sender()); + + // Now read all the data from the socket + QByteArray data; + data = client->readAll(); + incoming_data_parser_->Parse(data); + + if (incoming_data_parser_->close_connection()) { + client->close(); + } +} diff --git a/src/networkremote/networkremote.h b/src/networkremote/networkremote.h new file mode 100644 index 000000000..381e2a1ad --- /dev/null +++ b/src/networkremote/networkremote.h @@ -0,0 +1,42 @@ +#ifndef NETWORKREMOTE_H +#define NETWORKREMOTE_H + +#include +#include +#include + +#include "core/player.h" +#include "core/application.h" +#include "incomingdataparser.h" +#include "outgoingdatacreator.h" + +class NetworkRemote : public QThread { + Q_OBJECT +public: + static const char* kSettingsGroup; + static const int kDefaultServerPort; + + NetworkRemote(Application* app); + ~NetworkRemote(); + +public slots: + void SetupServer(); + void StartServer(); + void ReloadSettings(); + void AcceptConnection(); + void IncomingData(); + +private: + QTcpServer* server_; + QList* clients_; + IncomingDataParser* incoming_data_parser_; + OutgoingDataCreator* outgoing_data_creator_; + int port_; + bool use_remote_; + Application* app_; + + void StopServer(); + void ReadSettings(); +}; + +#endif // NETWORKREMOTE_H diff --git a/src/networkremote/networkremotehelper.cpp b/src/networkremote/networkremotehelper.cpp new file mode 100644 index 000000000..40ba8b592 --- /dev/null +++ b/src/networkremote/networkremotehelper.cpp @@ -0,0 +1,58 @@ +/* This file is part of Clementine. + Copyright 2012, Andreas Muttscheller + + Clementine 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. + + Clementine 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 Clementine. If not, see . +*/ + +#include "core/logging.h" + +#include "networkremote.h" +#include "networkremotehelper.h" + +NetworkRemoteHelper* NetworkRemoteHelper::sInstance = NULL; + +NetworkRemoteHelper::NetworkRemoteHelper(Application* app) + : app_(app) +{ + app_ = app; + connect(this, SIGNAL(ReloadSettingsSig()), + app_->network_remote(), SLOT(ReloadSettings())); + connect(this, SIGNAL(StartServerSig()), + app_->network_remote(), SLOT(StartServer())); + connect(this, SIGNAL(SetupServerSig()), + app_->network_remote(), SLOT(SetupServer())); + + sInstance = this; +} + +NetworkRemoteHelper::~NetworkRemoteHelper() { +} + +void NetworkRemoteHelper::StartServer() { + emit SetupServerSig(); + emit StartServerSig(); +} + +void NetworkRemoteHelper::ReloadSettings() { + emit ReloadSettingsSig(); +} + +// For using in Settingsdialog, we haven't the appication there +NetworkRemoteHelper* NetworkRemoteHelper::Instance() { + if (!sInstance) { + // normally he shouldn't go here. Only for safety + return NULL; + } + return sInstance; +} diff --git a/src/networkremote/networkremotehelper.h b/src/networkremote/networkremotehelper.h new file mode 100644 index 000000000..a11d8d3f5 --- /dev/null +++ b/src/networkremote/networkremotehelper.h @@ -0,0 +1,29 @@ +#ifndef NETWORKREMOTEHELPER_H +#define NETWORKREMOTEHELPER_H + +#include + +#include "networkremote.h" + +class NetworkRemoteHelper : public QObject { + Q_OBJECT +public: + static NetworkRemoteHelper* Instance(); + + NetworkRemoteHelper(Application* app); + ~NetworkRemoteHelper(); + + void StartServer(); + void ReloadSettings(); + +signals: + void SetupServerSig(); + void StartServerSig(); + void ReloadSettingsSig(); + +private: + static NetworkRemoteHelper* sInstance; + Application* app_; +}; + +#endif // NETWORKREMOTEHELPER_H diff --git a/src/networkremote/outgoingdatacreator.cpp b/src/networkremote/outgoingdatacreator.cpp new file mode 100644 index 000000000..fa5ac4599 --- /dev/null +++ b/src/networkremote/outgoingdatacreator.cpp @@ -0,0 +1,268 @@ +/* This file is part of Clementine. + Copyright 2012, Andreas Muttscheller + + Clementine 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. + + Clementine 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 Clementine. If not, see . +*/ + +#include "outgoingdatacreator.h" +#include "core/logging.h" + +OutgoingDataCreator::OutgoingDataCreator(Application* app) + : app_(app), + clients_(NULL) +{ + // Create Keep Alive Timer + keep_alive_timer_ = new QTimer(this); + connect(keep_alive_timer_, SIGNAL(timeout()), this, SLOT(SendKeepAlive())); + keep_alive_timeout_ = 10000; +} + +OutgoingDataCreator::~OutgoingDataCreator() { +} + +void OutgoingDataCreator::SetClients(QList* clients) { + clients_ = clients; + // After we got some clients, start the keep alive timer + // Default: every 10 seconds + keep_alive_timer_->start(keep_alive_timeout_); +} + +void OutgoingDataCreator::SendDataToClients(pb::remote::Message* msg) { + // Check if we have clients to send data to + if (!clients_ || clients_->size() == 0) { + return; + } + + QTcpSocket* sock; + foreach(sock, *clients_) { + // Check if the client is still active + if (sock->state() == QTcpSocket::ConnectedState) { + std::string data = msg->SerializeAsString(); + QByteArray b64_data = QByteArray::fromRawData(data.data(), data.length()); + sock->write(b64_data.toBase64()); + sock->write("\n"); + sock->flush(); + } else { + clients_->removeAt(clients_->indexOf(sock)); + } + } +} + +void OutgoingDataCreator::SendClementineInfos() { + // Create the general message and set the message type + pb::remote::Message msg; + msg.set_msgtype(pb::remote::INFOS); + + // Now add the message specific data + SetEngineState(&msg); + + QString version = QString("%1 %2").arg(QCoreApplication::applicationName(), + QCoreApplication::applicationVersion()); + pb::remote::ClementineInfos *infos = msg.mutable_infos(); + infos->set_version(version.toAscii()); + + SendDataToClients(&msg); +} + +void OutgoingDataCreator::SetEngineState(pb::remote::Message *msg) { + switch(app_->player()->GetState()) { + case Engine::Idle: msg->set_state(pb::remote::Idle); + break; + case Engine::Empty: msg->set_state(pb::remote::Empty); + break; + case Engine::Playing: msg->set_state(pb::remote::Playing); + break; + case Engine::Paused: msg->set_state(pb::remote::Paused); + break; + } +} + +void OutgoingDataCreator::SendAllPlaylists() { + // Get all Playlists + QList playlists = app_->playlist_manager()->GetAllPlaylists(); + QListIterator i(playlists); + int active_playlist = app_->playlist_manager()->active_id(); + + // Create message + pb::remote::Message msg; + msg.set_msgtype(pb::remote::PLAYLISTS); + + while(i.hasNext()) { + // Get the next Playlist + Playlist* p = i.next(); + QString playlist_name = app_->playlist_manager()->GetPlaylistName(p->id()); + + // Create a new playlist + pb::remote::Playlist* playlist = msg.add_playlists(); + playlist->set_name(playlist_name.toStdString()); + playlist->set_id(p->id()); + playlist->set_item_count(p->GetAllSongs().size()); + playlist->set_active((p->id() == active_playlist)); + } + + SendDataToClients(&msg); +} + +void OutgoingDataCreator::ActiveChanged(Playlist *) { + // When a playlist was changed, send the new list + SendAllPlaylists(); +} + +void OutgoingDataCreator::SendFirstData() { + // First Send the current song + PlaylistItemPtr item = app_->player()->GetCurrentItem(); + if (!item) { + qLog(Info) << "No current item found!"; + } + + CurrentSongChanged(current_song_, current_uri_, current_image_); + + // then the current volume + VolumeChanged(app_->player()->GetVolume()); +} + +void OutgoingDataCreator::CurrentSongChanged(const Song& song, const QString& uri, const QImage& img) { + current_song_ = song; + current_uri_ = uri; + current_image_ = img; + + if (clients_) { + // Create the message + pb::remote::Message msg; + msg.set_msgtype(pb::remote::CURRENT_METAINFOS); + + // If there is no song, create an empty node, otherwise fill it with data + int i = app_->playlist_manager()->active()->current_row(); + CreateSong(msg.mutable_currentsong(), ¤t_song_, &uri, i); + + SendDataToClients(&msg); + } +} + +void OutgoingDataCreator::CreateSong(pb::remote::SongMetadata* song_metadata, + Song* song, const QString* artUri, int index) { + if (song->is_valid()) { + song_metadata->set_id(song->id()); + song_metadata->set_index(index); + song_metadata->set_title( DataCommaSizeFromQString(song->PrettyTitle())); + song_metadata->set_artist(DataCommaSizeFromQString(song->artist())); + song_metadata->set_album( DataCommaSizeFromQString(song->album())); + song_metadata->set_albumartist(DataCommaSizeFromQString(song->albumartist())); + song_metadata->set_pretty_length(DataCommaSizeFromQString(song->PrettyLength())); + song_metadata->set_genre(DataCommaSizeFromQString(song->genre())); + song_metadata->set_pretty_year(DataCommaSizeFromQString(song->PrettyYear())); + song_metadata->set_track(song->track()); + song_metadata->set_disc(song->disc()); + song_metadata->set_playcount(song->playcount()); + + // Append coverart + if (!artUri->isEmpty()) { + QImage orig(QUrl(*artUri).toLocalFile()); + QImage small; + // Check if we resize the image + if (orig.width() > 1000) { + small = orig.scaled(1000, 1000, Qt::KeepAspectRatio); + } else { + small = orig; + } + + // Read the image in a buffer and compress it + QByteArray data; + QBuffer buf(&data); + buf.open(QIODevice::WriteOnly); + small.save(&buf, "JPG"); + + // Append the Data in the protocol buffer + song_metadata->set_art(data.toBase64()); + + buf.close(); + } + } +} + + +void OutgoingDataCreator::VolumeChanged(int volume) { + // Create the message + pb::remote::Message msg; + msg.set_msgtype(pb::remote::SET_VOLUME); + msg.set_volume(volume); + SendDataToClients(&msg); +} + +void OutgoingDataCreator::SendPlaylistSongs(int id) { + // Get the PlaylistQByteArray(data.data(), data.size() + Playlist* playlist = app_->playlist_manager()->playlist(id); + if(!playlist) { + qLog(Info) << "Could not find playlist with id = " << id; + return; + } + + SongList song_list = playlist->GetAllSongs(); + QListIterator i(song_list); + + // Create the message and the playlist + pb::remote::Message msg; + msg.set_msgtype(pb::remote::PLAYLIST_SONGS); + // Create a new playlist + pb::remote::Playlist* pb_playlist = msg.add_playlists(); + pb_playlist->set_id(id); + pb_playlist->set_item_count(playlist->GetAllSongs().size()); + + // Send all songs + int index = 0; + while(i.hasNext()) { + Song song = i.next(); + QString art = song.art_automatic(); + pb::remote::SongMetadata* pb_song = pb_playlist->add_songs(); + CreateSong(pb_song, &song, &art, index); + ++index; + } + SendDataToClients(&msg); +} + +void OutgoingDataCreator::PlaylistChanged(Playlist* playlist) { + // If a playlist changed, then send the new songs to the client + SendPlaylistSongs(playlist->id()); +} + +void OutgoingDataCreator::StateChanged(Engine::State state) { + // Send state only if it changed + // When selecting next song, StateChanged is emitted, but we already know + // that we are playing + if (state == last_state_) { + return; + } + last_state_ = state; + + pb::remote::Message msg; + + switch (state) { + case Engine::Playing: msg.set_msgtype(pb::remote::PLAY); + break; + case Engine::Paused: msg.set_msgtype(pb::remote::PAUSE); + break; + case Engine::Empty: msg.set_msgtype(pb::remote::STOP); // Empty is called when player stopped + break; + default: msg.set_msgtype(pb::remote::STOP); + break; + }; + + SendDataToClients(&msg); +} + +void OutgoingDataCreator::SendKeepAlive() { + pb::remote::Message msg; + msg.set_msgtype(pb::remote::KEEP_ALIVE); + SendDataToClients(&msg); +} diff --git a/src/networkremote/outgoingdatacreator.h b/src/networkremote/outgoingdatacreator.h new file mode 100644 index 000000000..7a3a8277d --- /dev/null +++ b/src/networkremote/outgoingdatacreator.h @@ -0,0 +1,52 @@ +#ifndef OUTGOINGDATACREATOR_H +#define OUTGOINGDATACREATOR_H + +#include +#include +#include +#include + +#include "core/player.h" +#include "core/application.h" +#include "engines/enginebase.h" +#include "engines/engine_fwd.h" +#include "playlist/playlist.h" +#include "playlist/playlistmanager.h" +#include "remotecontrolmessages.pb.h" + +class OutgoingDataCreator : public QObject { + Q_OBJECT +public: + OutgoingDataCreator(Application* app); + ~OutgoingDataCreator(); + + void SetClients(QList* clients); + +public slots: + void SendClementineInfos(); + void SendAllPlaylists(); + void SendFirstData(); + void SendPlaylistSongs(int id); + void PlaylistChanged(Playlist*); + void VolumeChanged(int volume); + void ActiveChanged(Playlist*); + void CurrentSongChanged(const Song& song, const QString& uri, const QImage& img); + void StateChanged(Engine::State); + void SendKeepAlive(); + +private: + Application* app_; + QList* clients_; + Song current_song_; + QString current_uri_; + QImage current_image_; + Engine::State last_state_; + QTimer* keep_alive_timer_; + int keep_alive_timeout_; + + void SendDataToClients(pb::remote::Message* msg); + void SetEngineState(pb::remote::Message* msg); + void CreateSong(pb::remote::SongMetadata* song_metadata, Song* song, const QString* art_uri, int index); +}; + +#endif // OUTGOINGDATACREATOR_H diff --git a/src/ui/networkremotesettingspage.cpp b/src/ui/networkremotesettingspage.cpp new file mode 100644 index 000000000..16a5266c0 --- /dev/null +++ b/src/ui/networkremotesettingspage.cpp @@ -0,0 +1,70 @@ +/* This file is part of Clementine. + Copyright 2010, David Sansome + + Clementine 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. + + Clementine 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 Clementine. If not, see . +*/ + +#include "iconloader.h" +#include "networkremotesettingspage.h" +#include "ui_networkremotesettingspage.h" +#include "networkremote/networkremote.h" +#include "networkremote/networkremotehelper.h" + +#include + + +NetworkRemoteSettingsPage::NetworkRemoteSettingsPage(SettingsDialog* dialog) + : SettingsPage(dialog), + ui_(new Ui_NetworkRemoteSettingsPage) +{ + ui_->setupUi(this); + setWindowIcon(IconLoader::Load("ipodtouchicon")); +} + +NetworkRemoteSettingsPage::~NetworkRemoteSettingsPage() { + delete ui_; +} + +void NetworkRemoteSettingsPage::Load() { + QSettings s; + int port; + + s.beginGroup(NetworkRemote::kSettingsGroup); + + port = s.value("port").toInt(); + if (port == 0) { + ui_->remote_port->setValue(NetworkRemote::kDefaultServerPort); + } + else { + ui_->remote_port->setValue(s.value("port").toInt()); + } + + ui_->use_remote->setChecked(s.value("use_remote").toBool()); + + s.endGroup(); +} + +void NetworkRemoteSettingsPage::Save() { + QSettings s; + + s.beginGroup(NetworkRemote::kSettingsGroup); + s.setValue("port", ui_->remote_port->value()); + s.setValue("use_remote", ui_->use_remote->isChecked()); + + s.endGroup(); + + if (NetworkRemoteHelper::Instance()) { + NetworkRemoteHelper::Instance()->ReloadSettings(); + } +} diff --git a/src/ui/networkremotesettingspage.h b/src/ui/networkremotesettingspage.h new file mode 100644 index 000000000..45e3dce62 --- /dev/null +++ b/src/ui/networkremotesettingspage.h @@ -0,0 +1,39 @@ +/* This file is part of Clementine. + Copyright 2010, David Sansome + + Clementine 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. + + Clementine 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 Clementine. If not, see . +*/ + +#ifndef NETWORKREMOTESETTINGSPAGE_H +#define NETWORKREMOTESETTINGSPAGE_H + +#include "settingspage.h" + +class Ui_NetworkRemoteSettingsPage; + +class NetworkRemoteSettingsPage : public SettingsPage { + Q_OBJECT + +public: + NetworkRemoteSettingsPage(SettingsDialog* dialog); + ~NetworkRemoteSettingsPage(); + + void Load(); + void Save(); + +private: + Ui_NetworkRemoteSettingsPage* ui_; +}; + +#endif // NETWORKREMOTESETTINGSPAGE_H diff --git a/src/ui/networkremotesettingspage.ui b/src/ui/networkremotesettingspage.ui new file mode 100644 index 000000000..c9577164e --- /dev/null +++ b/src/ui/networkremotesettingspage.ui @@ -0,0 +1,103 @@ + + + NetworkRemoteSettingsPage + + + + 0 + 0 + 400 + 300 + + + + Network Remote + + + + + + Use Remotecontrol + + + + + + + false + + + + 24 + + + + + + + + 171 + 0 + + + + Qt::LeftToRight + + + Port + + + Qt::AlignLeading|Qt::AlignLeft|Qt::AlignVCenter + + + + + + + 65535 + + + 8080 + + + + + + + + + + + + Qt::Vertical + + + + 20 + 36 + + + + + + + + + + use_remote + toggled(bool) + use_remote_container + setEnabled(bool) + + + 199 + 19 + + + 199 + 60 + + + + + diff --git a/src/ui/settingsdialog.cpp b/src/ui/settingsdialog.cpp index 9f51feb68..a7923be1b 100644 --- a/src/ui/settingsdialog.cpp +++ b/src/ui/settingsdialog.cpp @@ -23,6 +23,7 @@ #include "iconloader.h" #include "playbacksettingspage.h" #include "networkproxysettingspage.h" +#include "networkremotesettingspage.h" #include "notificationssettingspage.h" #include "mainwindow.h" #include "settingsdialog.h" @@ -130,6 +131,7 @@ SettingsDialog::SettingsDialog(Application* app, BackgroundStreams* streams, QWi AddPage(Page_Library, new LibrarySettingsPage(this), general); AddPage(Page_Proxy, new NetworkProxySettingsPage(this), general); AddPage(Page_Transcoding, new TranscoderSettingsPage(this), general); + AddPage(Page_NetworkRemote, new NetworkRemoteSettingsPage(this), general); #ifdef HAVE_WIIMOTEDEV AddPage(Page_Wiimotedev, new WiimoteSettingsPage(this), general); diff --git a/src/ui/settingsdialog.h b/src/ui/settingsdialog.h index 3f874a6bb..d5d63b59c 100644 --- a/src/ui/settingsdialog.h +++ b/src/ui/settingsdialog.h @@ -63,6 +63,7 @@ public: Page_GlobalShortcuts, Page_GlobalSearch, Page_Appearance, + Page_NetworkRemote, Page_Notifications, Page_Library, Page_Lastfm,