diff --git a/client/Makefile b/client/Makefile index 9f2cfc53..2ce94da1 100644 --- a/client/Makefile +++ b/client/Makefile @@ -31,7 +31,8 @@ else endif -CXXFLAGS += $(ADD_CFLAGS) -std=c++0x -Wall -Wno-unused-function -O3 -DASIO_STANDALONE -DVERSION=\"$(VERSION)\" -I. -I.. -isystem ../externals/asio/asio/include -I../externals/popl/include -I../externals/aixlog/include +# Include the process.hpp for usage in meta tags callback +CXXFLAGS += $(ADD_CFLAGS) -std=c++0x -Wall -Wno-unused-function -O3 -DASIO_STANDALONE -DVERSION=\"$(VERSION)\" -I. -I.. -isystem ../externals/asio/asio/include -I../externals/popl/include -I../externals/aixlog/include -I../server/streamreader LDFLAGS = -logg -lFLAC OBJ = snapClient.o stream.o clientConnection.o timeProvider.o player/player.o decoder/pcmDecoder.o decoder/oggDecoder.o decoder/flacDecoder.o controller.o ../message/pcmChunk.o ../common/sampleFormat.o diff --git a/client/controller.cpp b/client/controller.cpp index 4ac25843..6b0f32d4 100644 --- a/client/controller.cpp +++ b/client/controller.cpp @@ -138,9 +138,21 @@ void Controller::onMessageReceived(ClientConnection* connection, const msg::Base streamTags_.reset(new msg::StreamTags()); streamTags_->deserialize(baseMessage, buffer); - LOG(INFO) << "Tag received: artist = " << streamTags_->getArtist() << "\n"; - LOG(INFO) << "Tag received: album = " << streamTags_->getAlbum() << "\n"; - LOG(INFO) << "Tag received: track = " << streamTags_->getTrack() << "\n"; + LOG(INFO) << "Stream tags: artist = <" << streamTags_->getArtist() << ">, album = <" << streamTags_->getAlbum() << ">, track = <" << streamTags_->getTrack() << ">\n"; + + // And we should trigger the meta tags script if given + struct stat buffer; + if(stat(meta_callback_.c_str(), &buffer) == 0) + { + LOG(INFO) << "About to execute meta tag callback script!\n"; + + // Check if its there, set environment and execute it + // Probably can use the process thing from streamreader + } + else + { + LOG(INFO) << "Meta tag callback script not found!\n"; + } } if (baseMessage.type != message_type::kTime) @@ -163,10 +175,11 @@ bool Controller::sendTimeSyncMessage(long after) } -void Controller::start(const PcmDevice& pcmDevice, const std::string& host, size_t port, int latency) +void Controller::start(const PcmDevice& pcmDevice, const std::string& host, size_t port, int latency, const std::string& meta_callback) { pcmDevice_ = pcmDevice; latency_ = latency; + meta_callback_ = meta_callback; clientConnection_.reset(new ClientConnection(this, host, port)); controllerThread_ = thread(&Controller::worker, this); } diff --git a/client/controller.h b/client/controller.h index a231c8bd..12128297 100644 --- a/client/controller.h +++ b/client/controller.h @@ -48,7 +48,7 @@ class Controller : public MessageReceiver { public: Controller(const std::string& clientId, size_t instance); - void start(const PcmDevice& pcmDevice, const std::string& host, size_t port, int latency); + void start(const PcmDevice& pcmDevice, const std::string& host, size_t port, int latency, const std::string& meta_callback); void stop(); /// Implementation of MessageReceiver. @@ -63,6 +63,7 @@ private: void worker(); bool sendTimeSyncMessage(long after = 1000); std::string hostId_; + std::string meta_callback_; size_t instance_; std::atomic active_; std::thread controllerThread_; diff --git a/client/snapClient.cpp b/client/snapClient.cpp index 210ec94f..d46b5d1f 100644 --- a/client/snapClient.cpp +++ b/client/snapClient.cpp @@ -73,6 +73,7 @@ int main (int argc, char **argv) int exitcode = EXIT_SUCCESS; try { + string meta_callback(""); string soundcard("default"); string host(""); size_t port(1704); @@ -87,6 +88,7 @@ int main (int argc, char **argv) auto listSwitch = op.add("l", "list", "list pcm devices"); /*auto soundcardValue =*/ op.add>("s", "soundcard", "index or name of the soundcard", "default", &soundcard); #endif + /*auto metaValue =*/ op.add>("m", "meta", "script to call on meta tags", "", &meta_callback); /*auto hostValue =*/ op.add>("h", "host", "server hostname or ip address", "", &host); /*auto portValue =*/ op.add>("p", "port", "server port", 1704, &port); #ifdef HAS_DAEMON @@ -230,7 +232,7 @@ int main (int argc, char **argv) if (!g_terminated) { LOG(INFO) << "Latency: " << latency << "\n"; - controller->start(pcmDevice, host, port, latency); + controller->start(pcmDevice, host, port, latency, meta_callback); while(!g_terminated) chronos::sleep(100); controller->stop(); diff --git a/common/metatags.cpp b/common/metatags.cpp deleted file mode 100644 index e69de29b..00000000 diff --git a/common/metatags.h b/common/metatags.h deleted file mode 100644 index ede756c5..00000000 --- a/common/metatags.h +++ /dev/null @@ -1,35 +0,0 @@ -/*** - This file is part of snapcast - Copyright (C) 2014-2017 Johannes Pohl - - This program 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. - - This program 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 this program. If not, see . -***/ - -#ifndef METATAGS_DATA_H -#define METATAGS_DATA_H - -#include "message/streamTags.h" - -class MetaTags -{ -public: - MetaTags(); - virtual ~MetaTags(); - -private: -}; - - - -#endif diff --git a/message/streamTags.h b/message/streamTags.h index 536db2d3..a741a643 100644 --- a/message/streamTags.h +++ b/message/streamTags.h @@ -21,6 +21,19 @@ #include "jsonMessage.h" +/* + * Due to the PCM pipe implementation of snapcast input we cannot know track start/end + * it's all a long stream (although we detect idle situations) + * + * So, we cannot push metadata on start of track as we don't know when that is. + * + * I.E. we push metadata as we get an update, as we don't know when an update + * is complete (different meta supported in different stream interfaces) + * it is the streamreaders responsibility to update metadata and + * trigger a client notification. + * + * I.E. we need to suppply the client notification mechanism. + */ namespace msg { @@ -30,15 +43,26 @@ class StreamTags : public JsonMessage public: StreamTags() : JsonMessage(message_type::kStreamTags) { - msg["meta_artist"] = ""; - msg["meta_album"] = ""; - msg["meta_track"] = ""; + msg["meta_artist"] = ""; + msg["meta_album"] = ""; + msg["meta_track"] = ""; + msg["meta_albumart"] = ""; } virtual ~StreamTags() { } + json toJson() const + { + json j = { + {"artist", getArtist()}, + {"album", getAlbum()}, + {"track", getTrack()}, + }; + return j; + } + std::string getArtist() const { return msg["meta_artist"]; @@ -54,6 +78,11 @@ public: return msg["meta_track"]; } + std::string getAlbumArt() const + { + return msg["meta_albumart"]; + } + void setArtist(std::string artist) { msg["meta_artist"] = artist; @@ -68,6 +97,12 @@ public: { msg["meta_track"] = track; } + + // Ascii encoded image XXX: more details + void setAlbumArt(std::string art) + { + msg["meta_albumart"] = art; + } }; } diff --git a/server/Makefile b/server/Makefile index 368a729f..f89c2588 100644 --- a/server/Makefile +++ b/server/Makefile @@ -33,7 +33,7 @@ endif CXXFLAGS += $(ADD_CFLAGS) -std=c++0x -Wall -Wno-unused-function -O3 -DASIO_STANDALONE -DVERSION=\"$(VERSION)\" -I. -I.. -isystem ../externals/asio/asio/include -I../externals/popl/include -I../externals/aixlog/include -I../externals/jsonrpcpp/lib -I../externals LDFLAGS = -lvorbis -lvorbisenc -logg -lFLAC -OBJ = snapServer.o config.o controlServer.o controlSession.o streamServer.o streamSession.o streamreader/streamUri.o streamreader/streamManager.o streamreader/pcmStream.o streamreader/pipeStream.o streamreader/fileStream.o streamreader/processStream.o streamreader/airplayStream.o streamreader/spotifyStream.o streamreader/watchdog.o encoder/encoderFactory.o encoder/flacEncoder.o encoder/pcmEncoder.o encoder/oggEncoder.o ../common/metatags.o ../common/sampleFormat.o ../message/pcmChunk.o ../externals/jsonrpcpp/lib/jsonrp.o +OBJ = snapServer.o config.o controlServer.o controlSession.o streamServer.o streamSession.o streamreader/streamUri.o streamreader/streamManager.o streamreader/pcmStream.o streamreader/pipeStream.o streamreader/fileStream.o streamreader/processStream.o streamreader/airplayStream.o streamreader/spotifyStream.o streamreader/watchdog.o encoder/encoderFactory.o encoder/flacEncoder.o encoder/pcmEncoder.o encoder/oggEncoder.o ../common/sampleFormat.o ../message/pcmChunk.o ../externals/jsonrpcpp/lib/jsonrp.o ifneq (,$(TARGET)) diff --git a/server/streamServer.cpp b/server/streamServer.cpp index 5afaf991..00481a70 100644 --- a/server/streamServer.cpp +++ b/server/streamServer.cpp @@ -39,6 +39,23 @@ StreamServer::~StreamServer() } +void StreamServer::onMetaChanged(const PcmStream* pcmStream) +{ + /// Notification: {"jsonrpc":"2.0","method":"Stream.OnMetadata","params":{"id":"stream 1", "meta": {"album": "some album", "artist": "some artist", "track": "some track"...}} + + const auto meta = pcmStream->getMeta(); + for (auto s : sessions_) + { + if (s->pcmStream().get() == pcmStream) + s->sendAsync(meta); + } + + LOG(INFO) << "onMetaChanged (" << pcmStream->getName() << ")\n"; + json notification = jsonrpcpp::Notification("Stream.OnMetadata", jsonrpcpp::Parameter("id", pcmStream->getId(), "meta", meta->toJson())).to_json(); + controlServer_->send(notification.dump(), NULL); + cout << "Notification: " << notification.dump() << "\n"; +} + void StreamServer::onStateChanged(const PcmStream* pcmStream, const ReaderState& state) { /// Notification: {"jsonrpc":"2.0","method":"Stream.OnUpdate","params":{"id":"stream 1","stream":{"id":"stream 1","status":"idle","uri":{"fragment":"","host":"","path":"/tmp/snapfifo","query":{"buffer_ms":"20","codec":"flac","name":"stream 1","sampleformat":"48000:16:2"},"raw":"pipe:///tmp/snapfifo?name=stream 1","scheme":"pipe"}}}} @@ -251,6 +268,7 @@ void StreamServer::ProcessRequest(const jsonrpcpp::request_ptr request, jsonrpcp session_ptr session = getStreamSession(client->id); if (session && (session->pcmStream() != stream)) { + session->sendAsync(stream->getMeta()); session->sendAsync(stream->getHeader()); session->setPcmStream(stream); } @@ -303,6 +321,7 @@ void StreamServer::ProcessRequest(const jsonrpcpp::request_ptr request, jsonrpcp session_ptr session = getStreamSession(client->id); if (session && stream && (session->pcmStream() != stream)) { + session->sendAsync(stream->getMeta()); session->sendAsync(stream->getHeader()); session->setPcmStream(stream); } @@ -517,14 +536,7 @@ void StreamServer::onMessageReceived(StreamSession* connection, const msg::BaseM Config::instance().save(); - // Send the group stream tags - LOG(INFO) << "request kStreamTags\n"; - //auto metaTags = make_shared(); - //metaTags->setArtist(stream->getMeta()->getArtist()); - //connection->sendAsync(metaTags); connection->sendAsync(stream->getMeta()); - LOG(INFO) << "kStreamTags sent\n"; - connection->setPcmStream(stream); auto headerChunk = stream->getHeader(); connection->sendAsync(headerChunk); diff --git a/server/streamServer.h b/server/streamServer.h index 73fa2eb4..9c43a843 100644 --- a/server/streamServer.h +++ b/server/streamServer.h @@ -92,6 +92,7 @@ public: virtual void onMessageReceived(ControlSession* connection, const std::string& message); /// Implementation of PcmListener + virtual void onMetaChanged(const PcmStream* pcmStream); virtual void onStateChanged(const PcmStream* pcmStream, const ReaderState& state); virtual void onChunkRead(const PcmStream* pcmStream, msg::PcmChunk* chunk, double duration); virtual void onResync(const PcmStream* pcmStream, double ms); diff --git a/server/streamreader/pcmStream.cpp b/server/streamreader/pcmStream.cpp index 9e8f385f..173fc20b 100644 --- a/server/streamreader/pcmStream.cpp +++ b/server/streamreader/pcmStream.cpp @@ -171,7 +171,8 @@ json PcmStream::toJson() const json j = { {"uri", uri_.toJson()}, {"id", getId()}, - {"status", state} + {"status", state}, + {"meta", meta_->toJson()} }; return j; } diff --git a/server/streamreader/pcmStream.h b/server/streamreader/pcmStream.h index 79eb6d55..4810c5e0 100644 --- a/server/streamreader/pcmStream.h +++ b/server/streamreader/pcmStream.h @@ -51,6 +51,7 @@ enum ReaderState class PcmListener { public: + virtual void onMetaChanged(const PcmStream* pcmStream) = 0; virtual void onStateChanged(const PcmStream* pcmStream, const ReaderState& state) = 0; virtual void onChunkRead(const PcmStream* pcmStream, msg::PcmChunk* chunk, double duration) = 0; virtual void onResync(const PcmStream* pcmStream, double ms) = 0; @@ -86,7 +87,7 @@ public: virtual json toJson() const; //const msg::StreamTags *getMeta() - std::shared_ptr getMeta() + std::shared_ptr getMeta() const { return meta_; } diff --git a/server/streamreader/spotifyStream.cpp b/server/streamreader/spotifyStream.cpp index 14622400..c53e36bc 100644 --- a/server/streamreader/spotifyStream.cpp +++ b/server/streamreader/spotifyStream.cpp @@ -115,6 +115,7 @@ void SpotifyStream::onStderrMsg(const char* buffer, size_t n) // 2016-11-03 09-00-18 [out] INFO:librespot::session: Authenticated ! watchdog_->trigger(); string logmsg = utils::string::trim_copy(string(buffer, n)); + if ((logmsg.find("allocated stream") == string::npos) && (logmsg.find("Got channel") == string::npos) && (logmsg.find('\0') == string::npos) && @@ -123,16 +124,24 @@ void SpotifyStream::onStderrMsg(const char* buffer, size_t n) LOG(INFO) << "(" << getName() << ") " << logmsg << "\n"; } - // Check for metadata - if (logmsg.find("Loading track") != string::npos) + // Track tags "Julia Michaels" "Issues - Acoustic" + if (logmsg.find("Track tags") != string::npos) { - regex re("Loading track \"(.*)\""); + // Traditional Libreelec meta interface, only track name + regex re("Track tags \"(.*)\" \"(.*)\""); smatch m; if (regex_search(logmsg, m, re)) { - LOG(INFO) << "Loading track (" << m[1] << ")\n"; - getMeta()->setTrack(m[1]); + // Create a new meta struct? + LOG(INFO) << "Loading track <" << m[1] << "> <" << m[2] << ">\n"; + getMeta()->setArtist(m[1]); + getMeta()->setAlbum(""); + getMeta()->setTrack(m[2]); + + // Trigger a stream update + if (pcmListener_) + pcmListener_->onMetaChanged(this); } } }