mirror of
https://github.com/badaix/snapcast.git
synced 2025-08-01 15:49:56 +02:00
Support for text tags through snapcast.
Only added tags Spotify stream input from modified librespot for now.
This commit is contained in:
parent
af3ea660b9
commit
67083975b0
10 changed files with 206 additions and 92 deletions
|
@ -30,9 +30,11 @@ else
|
|||
TARGET_DIR ?= /usr
|
||||
endif
|
||||
|
||||
# Simplify building debuggable executables 'make DEBUG=-g STRIP=echo'
|
||||
DEBUG=-O3
|
||||
|
||||
# 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
|
||||
|
||||
CXXFLAGS += $(ADD_CFLAGS) -std=c++0x -Wall -Wno-unused-function $(DEBUG) -DASIO_STANDALONE -DVERSION=\"$(VERSION)\" -I. -I.. -isystem ../externals/asio/asio/include -I../externals/popl/include -I../externals/aixlog/include -I../externals
|
||||
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
|
||||
|
||||
|
|
|
@ -34,7 +34,7 @@
|
|||
using namespace std;
|
||||
|
||||
|
||||
Controller::Controller(const std::string& hostId, size_t instance) : MessageReceiver(),
|
||||
Controller::Controller(const std::string& hostId, size_t instance, std::shared_ptr<MetadataAdapter> meta) : MessageReceiver(),
|
||||
hostId_(hostId),
|
||||
instance_(instance),
|
||||
active_(false),
|
||||
|
@ -42,6 +42,7 @@ Controller::Controller(const std::string& hostId, size_t instance) : MessageRece
|
|||
stream_(nullptr),
|
||||
decoder_(nullptr),
|
||||
player_(nullptr),
|
||||
meta_(meta),
|
||||
serverSettings_(nullptr),
|
||||
async_exception_(nullptr)
|
||||
{
|
||||
|
@ -138,21 +139,8 @@ void Controller::onMessageReceived(ClientConnection* connection, const msg::Base
|
|||
streamTags_.reset(new msg::StreamTags());
|
||||
streamTags_->deserialize(baseMessage, buffer);
|
||||
|
||||
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(meta_)
|
||||
meta_->push(streamTags_->msg);
|
||||
}
|
||||
|
||||
if (baseMessage.type != message_type::kTime)
|
||||
|
@ -175,11 +163,10 @@ bool Controller::sendTimeSyncMessage(long after)
|
|||
}
|
||||
|
||||
|
||||
void Controller::start(const PcmDevice& pcmDevice, const std::string& host, size_t port, int latency, const std::string& meta_callback)
|
||||
void Controller::start(const PcmDevice& pcmDevice, const std::string& host, size_t port, int latency)
|
||||
{
|
||||
pcmDevice_ = pcmDevice;
|
||||
latency_ = latency;
|
||||
meta_callback_ = meta_callback;
|
||||
clientConnection_.reset(new ClientConnection(this, host, port));
|
||||
controllerThread_ = thread(&Controller::worker, this);
|
||||
}
|
||||
|
|
|
@ -35,6 +35,7 @@
|
|||
#endif
|
||||
#include "clientConnection.h"
|
||||
#include "stream.h"
|
||||
#include "metadata.h"
|
||||
|
||||
|
||||
/// Forwards PCM data to the audio player
|
||||
|
@ -47,8 +48,8 @@
|
|||
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, const std::string& meta_callback);
|
||||
Controller(const std::string& clientId, size_t instance, std::shared_ptr<MetadataAdapter> meta);
|
||||
void start(const PcmDevice& pcmDevice, const std::string& host, size_t port, int latency);
|
||||
void stop();
|
||||
|
||||
/// Implementation of MessageReceiver.
|
||||
|
@ -74,6 +75,7 @@ private:
|
|||
std::shared_ptr<Stream> stream_;
|
||||
std::unique_ptr<Decoder> decoder_;
|
||||
std::unique_ptr<Player> player_;
|
||||
std::shared_ptr<MetadataAdapter> meta_;
|
||||
std::shared_ptr<msg::ServerSettings> serverSettings_;
|
||||
std::shared_ptr<msg::StreamTags> streamTags_;
|
||||
std::shared_ptr<msg::CodecHeader> headerChunk_;
|
||||
|
|
109
client/metadata.h
Normal file
109
client/metadata.h
Normal file
|
@ -0,0 +1,109 @@
|
|||
/***
|
||||
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 METADATA_H
|
||||
#define METADATA_H
|
||||
|
||||
//#include <sys/types.h>
|
||||
//#include <sys/stat.h>
|
||||
//#include <fcntl.h>
|
||||
//#include <limits.h>
|
||||
//#include <cstdlib>
|
||||
//#include <cstring>
|
||||
//#include <iostream>
|
||||
//#include <streambuf>
|
||||
#include "json.hpp"
|
||||
|
||||
// Prefix used in output
|
||||
#define METADATA std::string("metadata")
|
||||
|
||||
/*
|
||||
* Implement a generic metadata output handler
|
||||
*/
|
||||
using json = nlohmann::json;
|
||||
|
||||
/*
|
||||
* Base class, prints to stdout
|
||||
*/
|
||||
class MetadataAdapter
|
||||
{
|
||||
public:
|
||||
MetadataAdapter()
|
||||
{
|
||||
reset();
|
||||
}
|
||||
|
||||
void reset()
|
||||
{
|
||||
_msg.reset(new json);
|
||||
}
|
||||
|
||||
std::string serialize()
|
||||
{
|
||||
return METADATA + ":" + _msg->dump() + "\n";
|
||||
}
|
||||
|
||||
void tag(std::string name, std::string value)
|
||||
{
|
||||
(*_msg)[name] = value;
|
||||
}
|
||||
|
||||
std::string operator[](std::string key)
|
||||
{
|
||||
try {
|
||||
return (*_msg)[key];
|
||||
}
|
||||
catch (std::domain_error&)
|
||||
{
|
||||
return std::string();
|
||||
}
|
||||
}
|
||||
|
||||
virtual int push()
|
||||
{
|
||||
std::cout << serialize() << "\n";
|
||||
return 0;
|
||||
}
|
||||
|
||||
int push(json& jtag)
|
||||
{
|
||||
_msg.reset(new json(jtag));
|
||||
return push();
|
||||
}
|
||||
|
||||
protected:
|
||||
std::shared_ptr<json> _msg;
|
||||
};
|
||||
|
||||
/*
|
||||
* Send metadata to stderr as json
|
||||
*/
|
||||
class MetaStderrAdapter: public MetadataAdapter
|
||||
{
|
||||
public:
|
||||
using MetadataAdapter::push;
|
||||
|
||||
int push()
|
||||
{
|
||||
std::cerr << serialize() << "\n";
|
||||
return 0;
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
#endif
|
|
@ -33,6 +33,7 @@
|
|||
#include "common/signalHandler.h"
|
||||
#include "common/strCompat.h"
|
||||
#include "common/utils.h"
|
||||
#include "metadata.h"
|
||||
|
||||
|
||||
using namespace std;
|
||||
|
@ -73,7 +74,7 @@ int main (int argc, char **argv)
|
|||
int exitcode = EXIT_SUCCESS;
|
||||
try
|
||||
{
|
||||
string meta_callback("");
|
||||
string meta_script("");
|
||||
string soundcard("default");
|
||||
string host("");
|
||||
size_t port(1704);
|
||||
|
@ -88,7 +89,8 @@ 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 metaStderr = op.add<Switch>("e", "mstderr", "send metadata to stderr");
|
||||
//auto metaHook = op.add<Value<string>>("m", "mhook", "script to call on meta tags", "", &meta_script);
|
||||
/*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
|
||||
|
@ -144,6 +146,9 @@ int main (int argc, char **argv)
|
|||
if (instance <= 0)
|
||||
std::invalid_argument("instance id must be >= 1");
|
||||
|
||||
|
||||
// XXX: Only one metadata option must be set
|
||||
|
||||
AixLog::Log::init<AixLog::SinkNative>("snapclient", AixLog::Severity::trace, AixLog::Type::special);
|
||||
if (debugOption->is_set())
|
||||
{
|
||||
|
@ -228,11 +233,17 @@ int main (int argc, char **argv)
|
|||
#endif
|
||||
}
|
||||
|
||||
std::unique_ptr<Controller> controller(new Controller(hostIdValue->value(), instance));
|
||||
// Setup metadata handling
|
||||
std::shared_ptr<MetadataAdapter> meta;
|
||||
meta.reset(new MetadataAdapter);
|
||||
if(metaStderr)
|
||||
meta.reset(new MetaStderrAdapter);
|
||||
|
||||
std::unique_ptr<Controller> controller(new Controller(hostIdValue->value(), instance, meta));
|
||||
if (!g_terminated)
|
||||
{
|
||||
LOG(INFO) << "Latency: " << latency << "\n";
|
||||
controller->start(pcmDevice, host, port, latency, meta_callback);
|
||||
controller->start(pcmDevice, host, port, latency);
|
||||
while(!g_terminated)
|
||||
chronos::sleep(100);
|
||||
controller->stop();
|
||||
|
|
|
@ -41,68 +41,33 @@ namespace msg
|
|||
class StreamTags : public JsonMessage
|
||||
{
|
||||
public:
|
||||
/*
|
||||
Usage:
|
||||
json jtag = {
|
||||
{"artist", "Pink Floyd"},
|
||||
{"album", "Dark Side of the Moon"},
|
||||
{"track", "Money"},
|
||||
{"spotifyid", "akjhasi7wehke7698"},
|
||||
{"musicbrainzid", "akjhasi7wehke7698"},
|
||||
};
|
||||
this->meta_.reset(new msg::StreamTags(jtag));
|
||||
|
||||
Stream input can decide on tags, IDK... but smart
|
||||
to use some common naming scheme
|
||||
*/
|
||||
|
||||
StreamTags(json j) : JsonMessage(message_type::kStreamTags)
|
||||
{
|
||||
msg = j;
|
||||
}
|
||||
|
||||
StreamTags() : JsonMessage(message_type::kStreamTags)
|
||||
{
|
||||
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"];
|
||||
}
|
||||
|
||||
std::string getAlbum() const
|
||||
{
|
||||
return msg["meta_album"];
|
||||
}
|
||||
|
||||
std::string getTrack() const
|
||||
{
|
||||
return msg["meta_track"];
|
||||
}
|
||||
|
||||
std::string getAlbumArt() const
|
||||
{
|
||||
return msg["meta_albumart"];
|
||||
}
|
||||
|
||||
void setArtist(std::string artist)
|
||||
{
|
||||
msg["meta_artist"] = artist;
|
||||
}
|
||||
|
||||
void setAlbum(std::string album)
|
||||
{
|
||||
msg["meta_album"] = album;
|
||||
}
|
||||
|
||||
void setTrack(std::string track)
|
||||
{
|
||||
msg["meta_track"] = track;
|
||||
}
|
||||
|
||||
// Ascii encoded image XXX: more details
|
||||
void setAlbumArt(std::string art)
|
||||
{
|
||||
msg["meta_albumart"] = art;
|
||||
}
|
||||
};
|
||||
|
||||
}
|
||||
|
|
|
@ -30,8 +30,11 @@ else
|
|||
TARGET_DIR ?= /usr
|
||||
endif
|
||||
|
||||
# Simplify building debuggable executables 'make DEBUG=-g STRIP=echo'
|
||||
DEBUG=-O3
|
||||
|
||||
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
|
||||
|
||||
CXXFLAGS += $(ADD_CFLAGS) -std=c++0x -Wall -Wno-unused-function $(DEBUG) -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/sampleFormat.o ../message/pcmChunk.o ../externals/jsonrpcpp/lib/jsonrp.o
|
||||
|
||||
|
|
|
@ -43,7 +43,10 @@ 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"...}}
|
||||
|
||||
// Send meta to all connected clients
|
||||
const auto meta = pcmStream->getMeta();
|
||||
//cout << "metadata = " << meta->msg.dump(3) << "\n";
|
||||
|
||||
for (auto s : sessions_)
|
||||
{
|
||||
if (s->pcmStream().get() == pcmStream)
|
||||
|
@ -51,9 +54,9 @@ void StreamServer::onMetaChanged(const PcmStream* pcmStream)
|
|||
}
|
||||
|
||||
LOG(INFO) << "onMetaChanged (" << pcmStream->getName() << ")\n";
|
||||
json notification = jsonrpcpp::Notification("Stream.OnMetadata", jsonrpcpp::Parameter("id", pcmStream->getId(), "meta", meta->toJson())).to_json();
|
||||
json notification = jsonrpcpp::Notification("Stream.OnMetadata", jsonrpcpp::Parameter("id", pcmStream->getId(), "meta", meta->msg)).to_json();
|
||||
controlServer_->send(notification.dump(), NULL);
|
||||
cout << "Notification: " << notification.dump() << "\n";
|
||||
////cout << "Notification: " << notification.dump() << "\n";
|
||||
}
|
||||
|
||||
void StreamServer::onStateChanged(const PcmStream* pcmStream, const ReaderState& state)
|
||||
|
@ -378,6 +381,33 @@ void StreamServer::ProcessRequest(const jsonrpcpp::request_ptr request, jsonrpcp
|
|||
else
|
||||
throw jsonrpcpp::MethodNotFoundException(request->id);
|
||||
}
|
||||
else if (request->method.find("Stream.") == 0)
|
||||
{
|
||||
if (request->method.find("Stream.SetMeta") == 0)
|
||||
{
|
||||
/// Request: {"id":4,"jsonrpc":"2.0","method":"Stream.SetMeta","params":{"id":"Spotify","meta": {"album": "some album", "artist": "some artist", "track": "some track"...}}}
|
||||
/// Response: {"id":4,"jsonrpc":"2.0","result":{"stream_id":"stream 1"}}
|
||||
/// Call onMetaChanged(const PcmStream* pcmStream) for updates and notifications
|
||||
|
||||
// Find stream
|
||||
string streamId = request->params.get("stream_id");
|
||||
PcmStreamPtr stream = streamManager_->getStream(streamId);
|
||||
if (stream == nullptr)
|
||||
throw jsonrpcpp::InternalErrorException("Stream not found", request->id);
|
||||
|
||||
// Set metadata from request
|
||||
// stream->
|
||||
|
||||
// Update clients
|
||||
// Setup response
|
||||
|
||||
// Trigger notifications
|
||||
// onMetaChanged(stream);
|
||||
// notification.reset(new jsonrpcpp::Notification("Client.OnLatencyChanged", jsonrpcpp::Parameter("id", clientInfo->id, "latency", clientInfo->config.latency)));
|
||||
}
|
||||
else
|
||||
throw jsonrpcpp::MethodNotFoundException(request->id);
|
||||
}
|
||||
else
|
||||
throw jsonrpcpp::MethodNotFoundException(request->id);
|
||||
|
||||
|
|
|
@ -56,8 +56,7 @@ PcmStream::PcmStream(PcmListener* pcmListener, const StreamUri& uri) :
|
|||
else
|
||||
dryoutMs_ = 2000;
|
||||
|
||||
// meta_.reset(new msg::StreamTags);
|
||||
meta_ = make_shared<msg::StreamTags>();
|
||||
meta_.reset(new msg::StreamTags());
|
||||
}
|
||||
|
||||
|
||||
|
@ -172,8 +171,11 @@ json PcmStream::toJson() const
|
|||
{"uri", uri_.toJson()},
|
||||
{"id", getId()},
|
||||
{"status", state},
|
||||
{"meta", meta_->toJson()}
|
||||
};
|
||||
|
||||
if(meta_)
|
||||
j["meta"] = meta_->msg;
|
||||
|
||||
return j;
|
||||
}
|
||||
|
||||
|
|
|
@ -135,9 +135,12 @@ void SpotifyStream::onStderrMsg(const char* buffer, size_t n)
|
|||
{
|
||||
// Create a new meta struct?
|
||||
LOG(INFO) << "Loading track <" << m[1] << "> <" << m[2] << ">\n";
|
||||
getMeta()->setArtist(m[1]);
|
||||
getMeta()->setAlbum("");
|
||||
getMeta()->setTrack(m[2]);
|
||||
|
||||
json jtag = {
|
||||
{"artist", (string)m[1]},
|
||||
{"track", (string)m[2]}
|
||||
};
|
||||
meta_.reset(new msg::StreamTags(jtag));
|
||||
|
||||
// Trigger a stream update
|
||||
if (pcmListener_)
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue