Change Stream.SetProperties to Stream.SetProperty

This commit is contained in:
badaix 2021-06-05 23:00:12 +02:00
parent 5b9321b8c6
commit bba3968f50
6 changed files with 102 additions and 65 deletions

View file

@ -19,6 +19,7 @@
#ifndef PROPERTIES_HPP
#define PROPERTIES_HPP
#include <boost/any.hpp>
#include <boost/optional.hpp>
#include <set>
#include <string>
@ -95,8 +96,6 @@ static std::ostream& operator<<(std::ostream& os, LoopStatus loop_status)
class Properties
{
static constexpr auto LOG_TAG = "Properties";
public:
Properties() = default;
Properties(const json& j)
@ -112,12 +111,16 @@ public:
/// A value of false indicates that playback is progressing linearly through a playlist, while true means playback is progressing through a playlist in some
/// other order.
boost::optional<bool> shuffle;
/// The current playback rate
boost::optional<int> rate;
/// The volume level between 0-100
boost::optional<int> volume;
/// The current track position in seconds
boost::optional<float> position;
// /// The current track duration in seconds
// boost::optional<float> duration;
/// The minimum value which the Rate property can take. Clients should not attempt to set the Rate property below this value
boost::optional<int> minimum_rate;
/// The maximum value which the Rate property can take. Clients should not attempt to set the Rate property above this value
boost::optional<int> maximum_rate;
/// Whether the client can call the Next method on this interface and expect the current track to change
boost::optional<bool> can_go_next;
/// Whether the client can call the Previous method on this interface and expect the current track to change
@ -134,14 +137,17 @@ public:
json toJson() const
{
json j;
if (playback_status.has_value())
addTag(j, "playbackStatus", boost::optional<std::string>(to_string(playback_status.value())));
if (loop_status.has_value())
addTag(j, "loopStatus", boost::optional<std::string>(to_string(loop_status.value())));
addTag(j, "shuffle", shuffle);
addTag(j, "volume", volume);
addTag(j, "rate", rate);
if (playback_status.has_value())
addTag(j, "playbackStatus", boost::optional<std::string>(to_string(playback_status.value())));
addTag(j, "position", position);
// addTag(j, "duration", duration);
addTag(j, "minimumRate", minimum_rate);
addTag(j, "maximumRate", maximum_rate);
addTag(j, "canGoNext", can_go_next);
addTag(j, "canGoPrevious", can_go_previous);
addTag(j, "canPlay", can_play);
@ -153,29 +159,19 @@ public:
void fromJson(const json& j)
{
static std::set<std::string> supported_props = {"playbackStatus", "loopStatus", "shuffle", "volume", "position", "canGoNext",
"canGoPrevious", "canPlay", "canPause", "canSeek", "canControl"};
static std::set<std::string> rw_props = {"loopStatus", "shuffle", "volume", "rate"};
static std::set<std::string> ro_props = {"playbackStatus", "loopStatus", "shuffle", "volume", "position", "minimumRate", "maximumRate",
"canGoNext", "canGoPrevious", "canPlay", "canPause", "canSeek", "canControl"};
for (const auto& element : j.items())
{
if (supported_props.find(element.key()) == supported_props.end())
bool is_rw = (rw_props.find(element.key()) != rw_props.end());
bool is_ro = (ro_props.find(element.key()) != ro_props.end());
if (!is_rw && !is_ro)
LOG(WARNING, LOG_TAG) << "Property not supoorted: " << element.key() << "\n";
}
boost::optional<std::string> opt;
readTag(j, "playbackStatus", opt);
if (opt.has_value())
{
if (*opt == "playing")
playback_status = PlaybackStatus::kPlaying;
else if (*opt == "paused")
playback_status = PlaybackStatus::kPaused;
else if (*opt == "stopped")
playback_status = PlaybackStatus::kStopped;
else
playback_status = PlaybackStatus::kUnknown;
}
else
playback_status = boost::none;
readTag(j, "loopStatus", opt);
if (opt.has_value())
{
@ -192,8 +188,25 @@ public:
loop_status = boost::none;
readTag(j, "shuffle", shuffle);
readTag(j, "volume", volume);
readTag(j, "rate", rate);
readTag(j, "playbackStatus", opt);
if (opt.has_value())
{
if (*opt == "playing")
playback_status = PlaybackStatus::kPlaying;
else if (*opt == "paused")
playback_status = PlaybackStatus::kPaused;
else if (*opt == "stopped")
playback_status = PlaybackStatus::kStopped;
else
playback_status = PlaybackStatus::kUnknown;
}
else
playback_status = boost::none;
readTag(j, "position", position);
// readTag(j, "duration", duration);
readTag(j, "minimumRate", minimum_rate);
readTag(j, "maximumRate", maximum_rate);
readTag(j, "canGoNext", can_go_next);
readTag(j, "canGoPrevious", can_go_previous);
readTag(j, "canPlay", can_play);
@ -237,6 +250,9 @@ private:
LOG(ERROR, LOG_TAG) << "failed to add tag: '" << tag << "': " << e.what() << '\n';
}
}
private:
static constexpr auto LOG_TAG = "Properties";
};

View file

@ -395,13 +395,13 @@ class MPDWrapper(object):
if offset >= 0:
strOffset = "+" + strOffset
self.seekcur(strOffset)
elif cmd == 'SetProperties':
properties = request['params']
logger.info(f'SetProperties: {properties}')
if 'shuffle' in properties:
self.random(int(properties['shuffle']))
if 'loopStatus' in properties:
value = properties['loopStatus']
elif cmd == 'SetProperty':
property = request['params']
logger.info(f'SetProperty: {property}')
if 'shuffle' in property:
self.random(int(property['shuffle']))
if 'loopStatus' in property:
value = property['loopStatus']
if value == "playlist":
self.repeat(1)
if self._can_single:
@ -438,7 +438,6 @@ class MPDWrapper(object):
for char in chunk:
if char == '\n':
logger.info(f'Received: {self._buffer}')
self.control(self._buffer)
self._buffer = ''
else:
@ -628,7 +627,7 @@ class MPDWrapper(object):
logger.debug(
f'key: {key}, value: {value}, mapped key: {mapped_key}, mapped value: {mapped_val}')
except KeyError:
logger.debug(f'tag "{key}" not supported')
logger.debug(f'property "{key}" not supported')
except (ValueError, TypeError):
logger.warning(
f"Can't cast value {value} to {status_mapping[key][1]}")

View file

@ -21,6 +21,7 @@
from time import sleep
from systemd.journal import _valid_field_name
import websocket
import logging
import threading
@ -467,11 +468,9 @@ class SnapcastWrapper(object):
"id": self._stream_id, "command": command, "params": params})
def set_property(self, property, value):
properties = {}
properties[property] = value
logger.info(f'set_properties {properties}')
self.send_request("Stream.SetProperties", {
"id": self._stream_id, "properties": properties})
logger.info(f'set_property {property} = {value}')
self.send_request("Stream.SetProperty", {
"id": self._stream_id, "property": property, "value": value})
@property
def metadata(self):

View file

@ -463,13 +463,13 @@ void Server::processRequest(const jsonrpcpp::request_ptr request, jsonrpcpp::ent
// Setup response
result["id"] = streamId;
}
else if (request->method().find("Stream.SetProperties") == 0)
else if (request->method().find("Stream.SetProperty") == 0)
{
// clang-format off
// clang-format on
LOG(INFO, LOG_TAG) << "Stream.SetProperties id: " << request->params().get<std::string>("id")
<< ", properties: " << request->params().get("properties") << "\n";
LOG(INFO, LOG_TAG) << "Stream.SetProperty id: " << request->params().get<std::string>("id")
<< ", property: " << request->params().get("property") << ", value: " << request->params().get("value") << "\n";
// Find stream
string streamId = request->params().get<std::string>("id");
@ -477,12 +477,10 @@ void Server::processRequest(const jsonrpcpp::request_ptr request, jsonrpcpp::ent
if (stream == nullptr)
throw jsonrpcpp::InternalErrorException("Stream not found", request->id());
Properties props(request->params().get("properties"));
// Set metadata from request
stream->setProperties(props);
stream->setProperty(request->params().get("property"), request->params().get("value"));
// Setup response
// TODO: error handling
result["id"] = streamId;
}
else if (request->method() == "Stream.AddStream")

View file

@ -262,13 +262,7 @@ void PcmStream::onControlMsg(const std::string& msg)
else if (notification->method() == "Player.Properties")
{
LOG(DEBUG, LOG_TAG) << "Received properties notification\n";
properties_ = std::make_shared<Properties>(notification->params().to_json());
// Trigger a stream update
for (auto* listener : pcmListeners_)
{
if (listener != nullptr)
listener->onPropertiesChanged(this);
}
setProperties(notification->params().to_json());
}
else
LOG(WARNING, LOG_TAG) << "Received unknown notification method: '" << notification->method() << "'\n";
@ -405,24 +399,38 @@ std::shared_ptr<Properties> PcmStream::getProperties() const
}
void PcmStream::setProperties(const Properties& props)
void PcmStream::setProperty(const std::string& name, const json& value)
{
LOG(INFO, LOG_TAG) << "Stream '" << getId() << "' set properties: " << props.toJson() << "\n";
LOG(INFO, LOG_TAG) << "Stream '" << getId() << "' set property: " << name << " = " << value << "\n";
// TODO: check validity
if (name == "loopStatus")
;
else if (name == "shuffle")
;
else if (name == "volume")
;
else if (name == "rate")
;
else
{
LOG(ERROR, LOG_TAG) << "Property not supported: " << name << "\n";
}
// TODO: queue commands, send next on timeout or after reception of the last command's response
if (ctrl_script_)
{
jsonrpcpp::Request request(++req_id_, "Player.SetProperties", props.toJson());
jsonrpcpp::Request request(++req_id_, "Player.SetProperty", {name, value});
ctrl_script_->send(request.to_json().dump() + "\n"); //, params);
}
else // TODO: Will the ctr_script always loop back the new properties?
{
properties_ = std::make_shared<Properties>(props);
// Trigger a stream update
for (auto* listener : pcmListeners_)
{
if (listener != nullptr)
listener->onPropertiesChanged(this);
}
// properties_ = std::make_shared<Properties>(props);
// // Trigger a stream update
// for (auto* listener : pcmListeners_)
// {
// if (listener != nullptr)
// listener->onPropertiesChanged(this);
// }
}
}
@ -446,7 +454,7 @@ void PcmStream::control(const std::string& command, const json& params)
void PcmStream::setMeta(const Metatags& meta)
{
meta_ = std::make_shared<Metatags>(meta);
LOG(INFO, LOG_TAG) << "Stream: " << name_ << ", metadata=" << meta_->toJson().dump(4) << "\n";
LOG(INFO, LOG_TAG) << "setMeta, stream: " << getId() << ", metadata: " << meta_->toJson() << "\n";
// Trigger a stream update
for (auto* listener : pcmListeners_)
@ -456,4 +464,20 @@ void PcmStream::setMeta(const Metatags& meta)
}
}
void PcmStream::setProperties(const Properties& props)
{
properties_ = std::make_shared<Properties>(props);
LOG(INFO, LOG_TAG) << "setProperties, stream: " << getId() << ", properties: " << props.toJson() << "\n";
// Trigger a stream update
for (auto* listener : pcmListeners_)
{
if (listener != nullptr)
listener->onPropertiesChanged(this);
}
}
} // namespace streamreader

View file

@ -172,7 +172,7 @@ public:
std::shared_ptr<Metatags> getMeta() const;
std::shared_ptr<Properties> getProperties() const;
void setProperties(const Properties& props);
void setProperty(const std::string& name, const json& value);
virtual void control(const std::string& command, const json& params);
@ -191,6 +191,7 @@ protected:
void chunkEncoded(const encoder::Encoder& encoder, std::shared_ptr<msg::PcmChunk> chunk, double duration);
void setMeta(const Metatags& meta);
void setProperties(const Properties& props);
std::chrono::time_point<std::chrono::steady_clock> tvEncodedChunk_;
std::vector<PcmListener*> pcmListeners_;