Basic metadata working librespot/server/client. Still missing client hooks.

The librespot metadata api kinda messy, no clear API. For now I addded
printing of artist in Librespot, should publish patch
This commit is contained in:
frafall 2017-11-25 05:13:04 +01:00
parent d444052233
commit af3ea660b9
13 changed files with 101 additions and 60 deletions

View file

@ -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

View file

@ -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);
}

View file

@ -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<bool> active_;
std::thread controllerThread_;

View file

@ -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<Switch>("l", "list", "list pcm devices");
/*auto soundcardValue =*/ op.add<Value<string>>("s", "soundcard", "index or name of the soundcard", "default", &soundcard);
#endif
/*auto metaValue =*/ op.add<Value<string>>("m", "meta", "script to call on meta tags", "", &meta_callback);
/*auto hostValue =*/ op.add<Value<string>>("h", "host", "server hostname or ip address", "", &host);
/*auto portValue =*/ op.add<Value<size_t>>("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();

View file

View file

@ -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 <http://www.gnu.org/licenses/>.
***/
#ifndef METATAGS_DATA_H
#define METATAGS_DATA_H
#include "message/streamTags.h"
class MetaTags
{
public:
MetaTags();
virtual ~MetaTags();
private:
};
#endif

View file

@ -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
{
@ -33,12 +46,23 @@ public:
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;
}
};
}

View file

@ -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))

View file

@ -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<msg::StreamTags>();
//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);

View file

@ -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);

View file

@ -171,7 +171,8 @@ json PcmStream::toJson() const
json j = {
{"uri", uri_.toJson()},
{"id", getId()},
{"status", state}
{"status", state},
{"meta", meta_->toJson()}
};
return j;
}

View file

@ -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<msg::StreamTags> getMeta()
std::shared_ptr<msg::StreamTags> getMeta() const
{
return meta_;
}

View file

@ -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);
}
}
}