diff --git a/control/meta_mpd.py b/control/meta_mpd.py index fa90c60a..2028d1b8 100755 --- a/control/meta_mpd.py +++ b/control/meta_mpd.py @@ -36,6 +36,7 @@ import time import json import musicbrainzngs import fcntl +import threading __version__ = "@version@" __git_version__ = "@gitversion@" @@ -287,6 +288,8 @@ class MPDWrapper(object): self.timer_callback() self.idle_enter() + send({"jsonrpc": "2.0", "method": "Plugin.Stream.Ready"}) + # Return False to stop trying to connect return False except socket.error as e: @@ -364,8 +367,8 @@ class MPDWrapper(object): try: request = json.loads(cmd) id = request['id'] - [interface, cmd] = request['method'].split('.', 1) - if interface == 'Player': + [interface, cmd] = request['method'].rsplit('.', 1) + if interface == 'Plugin.Stream.Player': if cmd == 'Control': success = True command = request['params']['command'] @@ -417,19 +420,26 @@ class MPDWrapper(object): self.repeat(0) if self._can_single: self.single(0) + elif cmd == 'GetProperties': + snapstatus = self._get_properties(self.status()) + logger.info(f'Snapstatus: {snapstatus}') + return send({"jsonrpc": "2.0", "id": id, "result": snapstatus}) + # return send({"jsonrpc": "2.0", "error": {"code": -32601, + # "message": "TODO: GetProperties not yet implemented"}, "id": id}) + elif cmd == 'GetMetadata': + send({"jsonrpc": "2.0", "method": "Plugin.Stream.Log", "params": {"severity":"Info","message":"Logmessage"}}) + return send({"jsonrpc": "2.0", "error": {"code": -32601, + "message": "TODO: GetMetadata not yet implemented"}, "id": id}) else: - send({"jsonrpc": "2.0", "error": {"code": -32601, - "message": "Method not found"}, "id": id}) - success = False + return send({"jsonrpc": "2.0", "error": {"code": -32601, + "message": "Method not found"}, "id": id}) else: - send({"jsonrpc": "2.0", "error": {"code": -32601, - "message": "Method not found"}, "id": id}) - success = False - if success: - send({"jsonrpc": "2.0", "result": "ok", "id": id}) - except: + return send({"jsonrpc": "2.0", "error": {"code": -32601, + "message": "Method not found"}, "id": id}) + send({"jsonrpc": "2.0", "result": "ok", "id": id}) + except Exception as e: send({"jsonrpc": "2.0", "error": { - "code": -32700, "message": "Parse error"}, "id": id}) + "code": -32700, "message": "Parse error", "data": str(e)}, "id": id}) def io_callback(self, fd, event): logger.debug("IO event %r on fd %r" % (event, fd)) @@ -510,7 +520,7 @@ class MPDWrapper(object): self._album_art_map[self.__track_key( snapmeta)] = snapmeta['artUrl'] send( - {"jsonrpc": "2.0", "method": "Player.Metadata", "params": snapmeta}) + {"jsonrpc": "2.0", "method": "Plugin.Stream.Player.Metadata", "params": snapmeta}) break except musicbrainzngs.musicbrainz.ResponseError as e: @@ -573,8 +583,11 @@ class MPDWrapper(object): if art_url != '': snapmeta['artUrl'] = art_url - send({"jsonrpc": "2.0", "method": "Player.Metadata", "params": snapmeta}) + send({"jsonrpc": "2.0", + "method": "Plugin.Stream.Player.Metadata", "params": snapmeta}) if not track_key in self._album_art_map: + # t = threading.Thread(target=self.update_albumart, args=[snapmeta]) + # t.start() self.update_albumart(snapmeta) def __diff_map(self, old_map, new_map): @@ -589,6 +602,29 @@ class MPDWrapper(object): diff[key] = [value, None] return diff + def _get_properties(self, mpd_status): + snapstatus = {} + for key, value in mpd_status.items(): + try: + mapped_key = status_mapping[key][0] + mapped_val = status_mapping[key][1](value) + snapstatus[mapped_key] = mapped_val + logger.debug( + f'key: {key}, value: {value}, mapped key: {mapped_key}, mapped value: {mapped_val}') + except KeyError: + logger.debug(f'property "{key}" not supported') + except (ValueError, TypeError): + logger.warning( + f"Can't cast value {value} to {status_mapping[key][1]}") + + snapstatus['canGoNext'] = True + snapstatus['canGoPrevious'] = True + snapstatus['canPlay'] = True + snapstatus['canPause'] = True + snapstatus['canSeek'] = 'duration' in snapstatus + snapstatus['canControl'] = True + return snapstatus + def _update_properties(self, force=False): logger.debug(f'update_properties force: {force}') old_position = self._position @@ -621,27 +657,9 @@ class MPDWrapper(object): logger.info(f'changed_song: {changed_song}') self._time = new_time = int(time.time()) - snapstatus = {} - for key, value in new_status.items(): - try: - mapped_key = status_mapping[key][0] - mapped_val = status_mapping[key][1](value) - snapstatus[mapped_key] = mapped_val - logger.debug( - f'key: {key}, value: {value}, mapped key: {mapped_key}, mapped value: {mapped_val}') - except KeyError: - logger.debug(f'property "{key}" not supported') - except (ValueError, TypeError): - logger.warning( - f"Can't cast value {value} to {status_mapping[key][1]}") - - snapstatus['canGoNext'] = True - snapstatus['canGoPrevious'] = True - snapstatus['canPlay'] = True - snapstatus['canPause'] = True - snapstatus['canSeek'] = 'duration' in snapstatus - snapstatus['canControl'] = True - send({"jsonrpc": "2.0", "method": "Player.Properties", "params": snapstatus}) + snapstatus = self._get_properties(new_status) + send({"jsonrpc": "2.0", "method": "Plugin.Stream.Player.Properties", + "params": snapstatus}) if 'elapsed' in new_status: new_position = float(new_status['elapsed']) diff --git a/server/streamreader/pcm_stream.cpp b/server/streamreader/pcm_stream.cpp index cfb49aff..745fe547 100644 --- a/server/streamreader/pcm_stream.cpp +++ b/server/streamreader/pcm_stream.cpp @@ -264,16 +264,36 @@ void PcmStream::onControlRequest(const jsonrpcpp::Request& request) void PcmStream::onControlNotification(const jsonrpcpp::Notification& notification) { LOG(INFO, LOG_TAG) << "Notification method: " << notification.method() << ", params: " << notification.params().to_json() << "\n"; - if (notification.method() == "Player.Metadata") + if (notification.method() == "Plugin.Stream.Player.Metadata") { LOG(DEBUG, LOG_TAG) << "Received metadata notification\n"; setMeta(notification.params().to_json()); } - else if (notification.method() == "Player.Properties") + else if (notification.method() == "Plugin.Stream.Player.Properties") { LOG(DEBUG, LOG_TAG) << "Received properties notification\n"; setProperties(notification.params().to_json()); } + else if (notification.method() == "Plugin.Stream.Ready") + { + LOG(DEBUG, LOG_TAG) << "Plugin is ready\n"; + ctrl_script_->send({++req_id_, "Plugin.Stream.Player.GetProperties"}, [this](const jsonrpcpp::Response& response) { + LOG(INFO, LOG_TAG) << "Response for Plugin.Stream.Player.GetProperties: " << response.to_json() << "\n"; + if (response.error().code() == 0) + setMeta(response.result()); + }); + ctrl_script_->send({++req_id_, "Plugin.Stream.Player.GetMetadata"}, [this](const jsonrpcpp::Response& response) { + LOG(INFO, LOG_TAG) << "Response for Plugin.Stream.Player.GetMetadata: " << response.to_json() << "\n"; + if (response.error().code() == 0) + setProperties(response.result()); + }); + } + else if (notification.method() == "Plugin.Stream.Log") + { + std::string severity = notification.params().get("severity"); + std::string message = notification.params().get("message"); + LOG(INFO, LOG_TAG) << "Plugin log - severity: " << severity << ", message: " << message << "\n"; + } else LOG(WARNING, LOG_TAG) << "Received unknown notification method: '" << notification.method() << "'\n"; } @@ -312,9 +332,11 @@ void PcmStream::start() active_ = true; if (ctrl_script_) + { ctrl_script_->start( getId(), server_settings_, [this](const jsonrpcpp::Notification& notification) { onControlNotification(notification); }, [this](const jsonrpcpp::Request& request) { onControlRequest(request); }, [this](std::string message) { onControlLog(std::move(message)); }); + } } @@ -462,7 +484,7 @@ void PcmStream::setProperty(const jsonrpcpp::Request& request, const CtrlScript: if (ctrl_script_) { - jsonrpcpp::Request req(++req_id_, "Player.SetProperty", {name, value}); + jsonrpcpp::Request req(++req_id_, "Plugin.Stream.Player.SetProperty", {name, value}); ctrl_script_->send(req, response_handler); } } @@ -489,7 +511,7 @@ void PcmStream::control(const jsonrpcpp::Request& request, const CtrlScript::OnR jsonrpcpp::Parameter params{"command", command}; if (request.params().has("params")) params.add("params", request.params().get("params")); - jsonrpcpp::Request req(++req_id_, "Player.Control", params); + jsonrpcpp::Request req(++req_id_, "Plugin.Stream.Player.Control", params); ctrl_script_->send(req, response_handler); } } @@ -497,6 +519,12 @@ void PcmStream::control(const jsonrpcpp::Request& request, const CtrlScript::OnR void PcmStream::setMeta(const Metatags& meta) { + if ((meta_ != nullptr) && (meta == *meta_)) + { + LOG(DEBUG, LOG_TAG) << "setMeta: Meta data did not change\n"; + return; + } + meta_ = std::make_shared(meta); LOG(INFO, LOG_TAG) << "setMeta, stream: " << getId() << ", metadata: " << meta_->toJson() << "\n"; @@ -511,6 +539,12 @@ void PcmStream::setMeta(const Metatags& meta) void PcmStream::setProperties(const Properties& props) { + if ((properties_ != nullptr) && (props == *properties_)) + { + LOG(DEBUG, LOG_TAG) << "setProperties: Properties did not change\n"; + return; + } + properties_ = std::make_shared(props); LOG(INFO, LOG_TAG) << "setProperties, stream: " << getId() << ", properties: " << props.toJson() << "\n"; diff --git a/server/streamreader/pcm_stream.hpp b/server/streamreader/pcm_stream.hpp index b7e863b8..2b388c08 100644 --- a/server/streamreader/pcm_stream.hpp +++ b/server/streamreader/pcm_stream.hpp @@ -216,7 +216,7 @@ protected: boost::asio::io_context& ioc_; ServerSettings server_settings_; std::unique_ptr ctrl_script_; - size_t req_id_; + int req_id_; }; } // namespace streamreader