mirror of
https://github.com/badaix/snapcast.git
synced 2025-05-28 08:26:16 +02:00
Add "mute" to stream API
This commit is contained in:
parent
60dc1aa48a
commit
9e3c0fdf55
12 changed files with 47 additions and 10 deletions
|
@ -519,6 +519,7 @@ See [Plugin.Stream.Player.SetProperty](stream_plugin.md#pluginstreamplayersetpro
|
||||||
{"id": 1, "jsonrpc": "2.0", "error": {"code": -32602, "message": "Value for loopStatus must be one of 'none', 'track', 'playlist'"}}
|
{"id": 1, "jsonrpc": "2.0", "error": {"code": -32602, "message": "Value for loopStatus must be one of 'none', 'track', 'playlist'"}}
|
||||||
{"id": 1, "jsonrpc": "2.0", "error": {"code": -32602, "message": "Value for shuffle must be bool"}}
|
{"id": 1, "jsonrpc": "2.0", "error": {"code": -32602, "message": "Value for shuffle must be bool"}}
|
||||||
{"id": 1, "jsonrpc": "2.0", "error": {"code": -32602, "message": "Value for volume must be an int"}}
|
{"id": 1, "jsonrpc": "2.0", "error": {"code": -32602, "message": "Value for volume must be an int"}}
|
||||||
|
{"id": 1, "jsonrpc": "2.0", "error": {"code": -32602, "message": "Value for mute must be bool"}}
|
||||||
{"id": 1, "jsonrpc": "2.0", "error": {"code": -32602, "message": "Value for rate must be float"}}
|
{"id": 1, "jsonrpc": "2.0", "error": {"code": -32602, "message": "Value for rate must be float"}}
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|
|
@ -82,7 +82,7 @@ Any [json-rpc 2.0 conformant error](https://www.jsonrpc.org/specification#error_
|
||||||
Snapserver will send the `SetProperty` command to the plugin, if `canControl` is `true`.
|
Snapserver will send the `SetProperty` command to the plugin, if `canControl` is `true`.
|
||||||
|
|
||||||
```json
|
```json
|
||||||
{"id": 1, "jsonrpc": "2.0", "method": "Plugin.Stream.Player.SetProperty", "params": {"<property>", <value>}}
|
{"id": 1, "jsonrpc": "2.0", "method": "Plugin.Stream.Player.SetProperty", "params": {"<property>": <value>}}
|
||||||
```
|
```
|
||||||
|
|
||||||
#### Supported `property`s
|
#### Supported `property`s
|
||||||
|
@ -93,7 +93,8 @@ Snapserver will send the `SetProperty` command to the plugin, if `canControl` is
|
||||||
* `playlist`: the playback loops through a list of tracks
|
* `playlist`: the playback loops through a list of tracks
|
||||||
* `shuffle`: [bool] play playlist in random order
|
* `shuffle`: [bool] play playlist in random order
|
||||||
* `volume`: [int] voume in percent, valid range [0..100]
|
* `volume`: [int] voume in percent, valid range [0..100]
|
||||||
* `rate`: [float] The current playback rate, valid range (0..)
|
* `mute`: [bool] the current mute state
|
||||||
|
* `rate`: [float] the current playback rate, valid range (0..)
|
||||||
|
|
||||||
#### Expected response
|
#### Expected response
|
||||||
|
|
||||||
|
@ -125,6 +126,7 @@ Any [json-rpc 2.0 conformant error](https://www.jsonrpc.org/specification#error_
|
||||||
* `playlist`: The playback loops through a list of tracks
|
* `playlist`: The playback loops through a list of tracks
|
||||||
* `shuffle`: [bool] Traverse through the playlist in random order
|
* `shuffle`: [bool] Traverse through the playlist in random order
|
||||||
* `volume`: [int] Voume in percent, valid range [0..100]
|
* `volume`: [int] Voume in percent, valid range [0..100]
|
||||||
|
* `mute`: [bool] Current mute state
|
||||||
* `rate`: [float] The current playback rate, valid range (0..)
|
* `rate`: [float] The current playback rate, valid range (0..)
|
||||||
* `position`: [float] Current playback position in seconds
|
* `position`: [float] Current playback position in seconds
|
||||||
* `canGoNext`: [bool] Whether the client can call the `next` method on this interface and expect the current track to change
|
* `canGoNext`: [bool] Whether the client can call the `next` method on this interface and expect the current track to change
|
||||||
|
@ -187,9 +189,9 @@ Any [json-rpc 2.0 conformant error](https://www.jsonrpc.org/specification#error_
|
||||||
##### Success
|
##### Success
|
||||||
|
|
||||||
```json
|
```json
|
||||||
{"id": 1, "jsonrpc": "2.0", "result": {"canControl":true,"canGoNext":true,"canGoPrevious":true,"canPause":true,"canPlay":true,"canSeek":false,"loopStatus":"none","playbackStatus":"playing","position":93.394,"shuffle":false,"volume":86}}
|
{"id": 1, "jsonrpc": "2.0", "result": {"canControl":true,"canGoNext":true,"canGoPrevious":true,"canPause":true,"canPlay":true,"canSeek":false,"loopStatus":"none","playbackStatus":"playing","position":93.394,"shuffle":false,"volume":86,"mute":false}}
|
||||||
|
|
||||||
{"id": 1, "jsonrpc": "2.0", "result": {"canControl":true,"canGoNext":true,"canGoPrevious":true,"canPause":true,"canPlay":true,"canSeek":true,"loopStatus":"none","metadata":{"album":"Doldinger","albumArtist":["Klaus Doldinger's Passport"],"artUrl":"http://coverartarchive.org/release/0d4ff56b-2a2b-43b5-bf99-063cac1599e5/16940576164-250.jpg","artist":["Klaus Doldinger's Passport feat. Nils Landgren"],"contentCreated":"2016","duration":305.2929992675781,"genre":["Jazz"],"musicbrainzAlbumId":"0d4ff56b-2a2b-43b5-bf99-063cac1599e5","title":"Soul Town","trackId":"7","trackNumber":6,"url":"Klaus Doldinger's Passport - Doldinger (2016)/06 - Soul Town.mp3"},"playbackStatus":"playing","position":72.79499816894531,"shuffle":false,"volume":97}}
|
{"id": 1, "jsonrpc": "2.0", "result": {"canControl":true,"canGoNext":true,"canGoPrevious":true,"canPause":true,"canPlay":true,"canSeek":true,"loopStatus":"none","metadata":{"album":"Doldinger","albumArtist":["Klaus Doldinger's Passport"],"artUrl":"http://coverartarchive.org/release/0d4ff56b-2a2b-43b5-bf99-063cac1599e5/16940576164-250.jpg","artist":["Klaus Doldinger's Passport feat. Nils Landgren"],"contentCreated":"2016","duration":305.2929992675781,"genre":["Jazz"],"musicbrainzAlbumId":"0d4ff56b-2a2b-43b5-bf99-063cac1599e5","title":"Soul Town","trackId":"7","trackNumber":6,"url":"Klaus Doldinger's Passport - Doldinger (2016)/06 - Soul Town.mp3"},"playbackStatus":"playing","position":72.79499816894531,"shuffle":false,"volume":97,"mute":false}}
|
||||||
```
|
```
|
||||||
|
|
||||||
##### Error
|
##### Error
|
||||||
|
@ -201,7 +203,7 @@ Any [json-rpc 2.0 conformant error](https://www.jsonrpc.org/specification#error_
|
||||||
### Plugin.Stream.Player.Properties
|
### Plugin.Stream.Player.Properties
|
||||||
|
|
||||||
```json
|
```json
|
||||||
{"jsonrpc": "2.0", "method": "Plugin.Stream.Player.Properties", "params": {"canControl":true,"canGoNext":true,"canGoPrevious":true,"canPause":true,"canPlay":true,"canSeek":false,"loopStatus":"none","playbackStatus":"playing","position":593.394,"shuffle":false,"volume":86}}
|
{"jsonrpc": "2.0", "method": "Plugin.Stream.Player.Properties", "params": {"canControl":true,"canGoNext":true,"canGoPrevious":true,"canPause":true,"canPlay":true,"canSeek":false,"loopStatus":"none","playbackStatus":"playing","position":593.394,"shuffle":false,"volume":86,"mute":false}}
|
||||||
```
|
```
|
||||||
|
|
||||||
Same format as in [GetProperties](#pluginstreamplayergetproperties). If `metadata` is missing, the last known metadata will be used, so the plugin must not send the complete metadata if one of the properties is updated.
|
Same format as in [GetProperties](#pluginstreamplayergetproperties). If `metadata` is missing, the last known metadata will be used, so the plugin must not send the complete metadata if one of the properties is updated.
|
||||||
|
|
|
@ -185,6 +185,8 @@ class MopidyControl(object):
|
||||||
properties['shuffle'] = result
|
properties['shuffle'] = result
|
||||||
elif request == 'core.mixer.get_volume':
|
elif request == 'core.mixer.get_volume':
|
||||||
properties['volume'] = result
|
properties['volume'] = result
|
||||||
|
elif request == 'core.mixer.get_mute':
|
||||||
|
properties['mute'] = result
|
||||||
elif request == 'core.playback.get_time_position':
|
elif request == 'core.playback.get_time_position':
|
||||||
properties['position'] = float(result) / 1000
|
properties['position'] = float(result) / 1000
|
||||||
elif request == 'core.playback.get_current_track':
|
elif request == 'core.playback.get_current_track':
|
||||||
|
@ -283,7 +285,7 @@ class MopidyControl(object):
|
||||||
jmsg['tl_track']['track'])
|
jmsg['tl_track']['track'])
|
||||||
logger.debug(f'Meta: {self._metadata}')
|
logger.debug(f'Meta: {self._metadata}')
|
||||||
self.send_batch_request([("core.playback.get_state", None), ("core.tracklist.get_repeat", None), ("core.tracklist.get_single", None),
|
self.send_batch_request([("core.playback.get_state", None), ("core.tracklist.get_repeat", None), ("core.tracklist.get_single", None),
|
||||||
("core.tracklist.get_random", None), ("core.mixer.get_volume", None), ("core.playback.get_time_position", None), ('core.library.get_images', {
|
("core.tracklist.get_random", None), ("core.mixer.get_volume", None), ("core.mixer.get_mute", None), ("core.playback.get_time_position", None), ('core.library.get_images', {
|
||||||
'uris': [self._metadata['url']]})], self.onPropertiesResponse)
|
'uris': [self._metadata['url']]})], self.onPropertiesResponse)
|
||||||
elif event in ['tracklist_changed', 'track_playback_ended']:
|
elif event in ['tracklist_changed', 'track_playback_ended']:
|
||||||
logger.debug("Nothing to do")
|
logger.debug("Nothing to do")
|
||||||
|
@ -293,7 +295,7 @@ class MopidyControl(object):
|
||||||
logger.debug("Nothing to do")
|
logger.debug("Nothing to do")
|
||||||
else:
|
else:
|
||||||
self.send_batch_request([("core.playback.get_state", None), ("core.tracklist.get_repeat", None), ("core.tracklist.get_single", None),
|
self.send_batch_request([("core.playback.get_state", None), ("core.tracklist.get_repeat", None), ("core.tracklist.get_single", None),
|
||||||
("core.tracklist.get_random", None), ("core.mixer.get_volume", None), ("core.playback.get_time_position", None)], self.onPropertiesResponse)
|
("core.tracklist.get_random", None), ("core.mixer.get_volume", None), ("core.mixer.get_mute", None), ("core.playback.get_time_position", None)], self.onPropertiesResponse)
|
||||||
|
|
||||||
def on_ws_error(self, ws, error):
|
def on_ws_error(self, ws, error):
|
||||||
logger.error("Snapcast RPC websocket error")
|
logger.error("Snapcast RPC websocket error")
|
||||||
|
@ -402,6 +404,9 @@ class MopidyControl(object):
|
||||||
if 'volume' in property:
|
if 'volume' in property:
|
||||||
self.send_request("core.mixer.set_volume", {
|
self.send_request("core.mixer.set_volume", {
|
||||||
"volume": int(property['volume'])})
|
"volume": int(property['volume'])})
|
||||||
|
if 'mute' in property:
|
||||||
|
self.send_request("core.mixer.set_mute", {
|
||||||
|
"mute": property['mute']})
|
||||||
elif cmd == 'GetProperties':
|
elif cmd == 'GetProperties':
|
||||||
self.send_batch_request([("core.playback.get_state", None), ("core.tracklist.get_repeat", None), ("core.tracklist.get_single", None),
|
self.send_batch_request([("core.playback.get_state", None), ("core.tracklist.get_repeat", None), ("core.tracklist.get_single", None),
|
||||||
("core.tracklist.get_random", None), ("core.mixer.get_volume", None), ("core.playback.get_current_track", None), ("core.playback.get_time_position", None)], lambda req_res: self.onSnapcastPropertiesResponse(id, req_res))
|
("core.tracklist.get_random", None), ("core.mixer.get_volume", None), ("core.playback.get_current_track", None), ("core.playback.get_time_position", None)], lambda req_res: self.onSnapcastPropertiesResponse(id, req_res))
|
||||||
|
|
|
@ -800,6 +800,7 @@ Usage: %(progname)s [OPTION]...
|
||||||
--snapcast-port=PORT Set the snapcast server port
|
--snapcast-port=PORT Set the snapcast server port
|
||||||
--stream=ID Set the stream id
|
--stream=ID Set the stream id
|
||||||
|
|
||||||
|
-h, --help Show this help message
|
||||||
-d, --debug Run in debug mode
|
-d, --debug Run in debug mode
|
||||||
-v, --version meta_mpd version
|
-v, --version meta_mpd version
|
||||||
|
|
||||||
|
|
|
@ -564,6 +564,12 @@ void Server::processRequest(const jsonrpcpp::request_ptr request, const OnRespon
|
||||||
throw jsonrpcpp::InvalidParamsException("Value for volume must be an int", request->id());
|
throw jsonrpcpp::InvalidParamsException("Value for volume must be an int", request->id());
|
||||||
stream->setVolume(value.get<int16_t>(), [handle_response](const snapcast::ErrorCode& ec) { handle_response(ec); });
|
stream->setVolume(value.get<int16_t>(), [handle_response](const snapcast::ErrorCode& ec) { handle_response(ec); });
|
||||||
}
|
}
|
||||||
|
else if (name == "mute")
|
||||||
|
{
|
||||||
|
if (!value.is_boolean())
|
||||||
|
throw jsonrpcpp::InvalidParamsException("Value for mute must be bool", request->id());
|
||||||
|
stream->setMute(value.get<bool>(), [handle_response](const snapcast::ErrorCode& ec) { handle_response(ec); });
|
||||||
|
}
|
||||||
else if (name == "rate")
|
else if (name == "rate")
|
||||||
{
|
{
|
||||||
if (!value.is_number_float())
|
if (!value.is_number_float())
|
||||||
|
|
|
@ -226,6 +226,12 @@ void MetaStream::setVolume(uint16_t volume, ResultHandler handler)
|
||||||
active_stream_->setVolume(volume, std::move(handler));
|
active_stream_->setVolume(volume, std::move(handler));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void MetaStream::setMute(bool mute, ResultHandler handler)
|
||||||
|
{
|
||||||
|
std::lock_guard<std::recursive_mutex> lock(mutex_);
|
||||||
|
active_stream_->setMute(mute, std::move(handler));
|
||||||
|
}
|
||||||
|
|
||||||
void MetaStream::setRate(float rate, ResultHandler handler)
|
void MetaStream::setRate(float rate, ResultHandler handler)
|
||||||
{
|
{
|
||||||
std::lock_guard<std::recursive_mutex> lock(mutex_);
|
std::lock_guard<std::recursive_mutex> lock(mutex_);
|
||||||
|
|
|
@ -51,6 +51,7 @@ public:
|
||||||
void setShuffle(bool shuffle, ResultHandler handler) override;
|
void setShuffle(bool shuffle, ResultHandler handler) override;
|
||||||
void setLoopStatus(LoopStatus status, ResultHandler handler) override;
|
void setLoopStatus(LoopStatus status, ResultHandler handler) override;
|
||||||
void setVolume(uint16_t volume, ResultHandler handler) override;
|
void setVolume(uint16_t volume, ResultHandler handler) override;
|
||||||
|
void setMute(bool mute, ResultHandler handler) override;
|
||||||
void setRate(float rate, ResultHandler handler) override;
|
void setRate(float rate, ResultHandler handler) override;
|
||||||
|
|
||||||
// Control commands
|
// Control commands
|
||||||
|
|
|
@ -347,6 +347,15 @@ void PcmStream::setVolume(uint16_t volume, ResultHandler handler)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
void PcmStream::setMute(bool mute, ResultHandler handler)
|
||||||
|
{
|
||||||
|
LOG(DEBUG, LOG_TAG) << "setMute: " << mute << "\n";
|
||||||
|
if (!properties_.can_control)
|
||||||
|
return handler({ControlErrc::can_control_is_false});
|
||||||
|
sendRequest("Plugin.Stream.Player.SetProperty", {"mute", mute}, std::move(handler));
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
void PcmStream::setRate(float rate, ResultHandler handler)
|
void PcmStream::setRate(float rate, ResultHandler handler)
|
||||||
{
|
{
|
||||||
LOG(DEBUG, LOG_TAG) << "setRate: " << rate << "\n";
|
LOG(DEBUG, LOG_TAG) << "setRate: " << rate << "\n";
|
||||||
|
|
|
@ -141,6 +141,7 @@ public:
|
||||||
virtual void setShuffle(bool shuffle, ResultHandler handler);
|
virtual void setShuffle(bool shuffle, ResultHandler handler);
|
||||||
virtual void setLoopStatus(LoopStatus status, ResultHandler handler);
|
virtual void setLoopStatus(LoopStatus status, ResultHandler handler);
|
||||||
virtual void setVolume(uint16_t volume, ResultHandler handler);
|
virtual void setVolume(uint16_t volume, ResultHandler handler);
|
||||||
|
virtual void setMute(bool mute, ResultHandler handler);
|
||||||
virtual void setRate(float rate, ResultHandler handler);
|
virtual void setRate(float rate, ResultHandler handler);
|
||||||
|
|
||||||
// Control commands
|
// Control commands
|
||||||
|
|
|
@ -92,6 +92,7 @@ json Properties::toJson() const
|
||||||
addTag(j, "rate", rate);
|
addTag(j, "rate", rate);
|
||||||
addTag(j, "shuffle", shuffle);
|
addTag(j, "shuffle", shuffle);
|
||||||
addTag(j, "volume", volume);
|
addTag(j, "volume", volume);
|
||||||
|
addTag(j, "mute", mute);
|
||||||
addTag(j, "position", position);
|
addTag(j, "position", position);
|
||||||
addTag(j, "minimumRate", minimum_rate);
|
addTag(j, "minimumRate", minimum_rate);
|
||||||
addTag(j, "maximumRate", maximum_rate);
|
addTag(j, "maximumRate", maximum_rate);
|
||||||
|
@ -108,9 +109,9 @@ json Properties::toJson() const
|
||||||
|
|
||||||
void Properties::fromJson(const json& j)
|
void Properties::fromJson(const json& j)
|
||||||
{
|
{
|
||||||
static std::set<std::string> rw_props = {"loopStatus", "shuffle", "volume", "rate"};
|
static std::set<std::string> rw_props = {"loopStatus", "shuffle", "volume", "mute", "rate"};
|
||||||
static std::set<std::string> ro_props = {"playbackStatus", "loopStatus", "shuffle", "volume", "position", "minimumRate", "maximumRate",
|
static std::set<std::string> ro_props = {"playbackStatus", "loopStatus", "shuffle", "volume", "mute", "position", "minimumRate", "maximumRate",
|
||||||
"canGoNext", "canGoPrevious", "canPlay", "canPause", "canSeek", "canControl", "metadata"};
|
"canGoNext", "canGoPrevious", "canPlay", "canPause", "canSeek", "canControl", "metadata"};
|
||||||
for (const auto& element : j.items())
|
for (const auto& element : j.items())
|
||||||
{
|
{
|
||||||
bool is_rw = (rw_props.find(element.key()) != rw_props.end());
|
bool is_rw = (rw_props.find(element.key()) != rw_props.end());
|
||||||
|
@ -135,6 +136,7 @@ void Properties::fromJson(const json& j)
|
||||||
readTag(j, "rate", rate);
|
readTag(j, "rate", rate);
|
||||||
readTag(j, "shuffle", shuffle);
|
readTag(j, "shuffle", shuffle);
|
||||||
readTag(j, "volume", volume);
|
readTag(j, "volume", volume);
|
||||||
|
readTag(j, "mute", mute);
|
||||||
readTag(j, "position", position);
|
readTag(j, "position", position);
|
||||||
readTag(j, "minimumRate", minimum_rate);
|
readTag(j, "minimumRate", minimum_rate);
|
||||||
readTag(j, "maximumRate", maximum_rate);
|
readTag(j, "maximumRate", maximum_rate);
|
||||||
|
|
|
@ -166,6 +166,8 @@ public:
|
||||||
std::optional<bool> shuffle;
|
std::optional<bool> shuffle;
|
||||||
/// The volume level between 0-100
|
/// The volume level between 0-100
|
||||||
std::optional<int> volume;
|
std::optional<int> volume;
|
||||||
|
/// The current mute state
|
||||||
|
std::optional<bool> mute;
|
||||||
/// The current track position in seconds
|
/// The current track position in seconds
|
||||||
std::optional<float> position;
|
std::optional<float> position;
|
||||||
/// The minimum value which the Rate property can take. Clients should not attempt to set the Rate property below this value
|
/// The minimum value which the Rate property can take. Clients should not attempt to set the Rate property below this value
|
||||||
|
|
|
@ -190,6 +190,7 @@ TEST_CASE("Properties")
|
||||||
"loopStatus": "track",
|
"loopStatus": "track",
|
||||||
"shuffle": false,
|
"shuffle": false,
|
||||||
"volume": 42,
|
"volume": 42,
|
||||||
|
"mute": false,
|
||||||
"position": 23.0
|
"position": 23.0
|
||||||
}
|
}
|
||||||
)");
|
)");
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue