diff --git a/ovos_core/intent_services/ocp_service.py b/ovos_core/intent_services/ocp_service.py index 919e3b66a66..90003b3a0ff 100644 --- a/ovos_core/intent_services/ocp_service.py +++ b/ovos_core/intent_services/ocp_service.py @@ -3,7 +3,7 @@ import time from os.path import join, dirname from threading import RLock -from typing import List, Tuple, Optional +from typing import List, Tuple, Optional, Union from ovos_classifiers.skovos.classifier import SklearnOVOSClassifier from ovos_classifiers.skovos.features import ClassifierProbaVectorizer, KeywordFeaturesVectorizer @@ -19,9 +19,15 @@ from ovos_utils import classproperty from ovos_utils.log import LOG from ovos_utils.messagebus import FakeBus -from ovos_utils.ocp import MediaType, PlaybackType, PlaybackMode, PlayerState, OCP_ID, MediaEntry, Playlist, MediaState +from ovos_utils.ocp import (MediaType, PlaybackType, PlaybackMode, PlayerState, OCP_ID, + MediaEntry, Playlist, MediaState, TrackState) from ovos_workshop.app import OVOSAbstractApplication +try: + from ovos_utils.ocp import dict2entry +except ImportError: # older ovos-utils + dict2entry = MediaEntry.from_dict + class OCPFeaturizer: # ignore_list accounts for "noise" keywords in the csv file @@ -172,6 +178,7 @@ def register_ocp_api_events(self): self.bus.on("ovos.common_play.search", self.handle_search_query) self.bus.on("ovos.common_play.play_search", self.handle_play_search) self.bus.on('ovos.common_play.status.response', self.handle_player_state_update) + self.bus.on('ovos.common_play.track.state', self.handle_track_state_update) self.bus.on('ovos.common_play.SEI.get.response', self.handle_get_SEIs) self.bus.on('ovos.common_play.register_keyword', self.handle_skill_keyword_register) @@ -183,7 +190,7 @@ def register_ocp_api_events(self): self.bus.on("mycroft.audio.service.pause", self._handle_legacy_audio_pause) self.bus.on("mycroft.audio.service.resume", self._handle_legacy_audio_resume) self.bus.on("mycroft.audio.service.stop", self._handle_legacy_audio_stop) - self.bus.emit(Message("ovos.common_play.status")) # sync on launch + self.bus.emit(Message("ovos.common_play.status")) # sync player state on launch def register_ocp_intents(self): intent_files = self.load_resource_files() @@ -302,6 +309,21 @@ def handle_skill_keyword_deregister(self, message: Message): # we need to keep the keyword available to the classifier # OCPFeaturizer.ocp_keywords.deregister_entity(kw_label) + def handle_track_state_update(self, message: Message): + """ovos.common_play.track.state""" + state = message.data.get("state") + if state is None: + raise ValueError(f"Got state update message with no state: " + f"{message}") + if isinstance(state, int): + state = TrackState(state) + if self.player_state != PlayerState.PLAYING and \ + state in [TrackState.PLAYING_AUDIO, TrackState.PLAYING_AUDIOSERVICE, + TrackState.PLAYING_VIDEO, TrackState.PLAYING_WEBVIEW, + TrackState.PLAYING_MPRIS]: + self.player_state = PlayerState.PLAYING + LOG.info(f"OCP PlayerState: {self.player_state}") + def handle_player_state_update(self, message: Message): """ Handles 'ovos.common_play.status' messages with player status updates @@ -567,6 +589,9 @@ def handle_stop_intent(self, message: Message): else: LOG.info("Requesting OCP to stop") self.ocp_api.stop() + # old ocp doesnt report stopped state ... + # TODO - remove this once proper events are emitted + self.player_state = PlayerState.STOPPED def handle_next_intent(self, message: Message): if self.use_legacy_audio: @@ -731,14 +756,19 @@ def _should_resume(self, phrase: str, lang: str) -> bool: return False # search - def filter_results(self, results: list, phrase: str, lang: str, - media_type: MediaType = MediaType.GENERIC) -> list: - + def normalize_results(self, results: list) -> List[Union[MediaEntry, Playlist]]: # support Playlist and MediaEntry objects in tracks for idx, track in enumerate(results): if isinstance(track, dict): - results[idx] = MediaEntry.from_dict(track) + try: + results[idx] = dict2entry(track) + except Exception as e: + LOG.error(f"got an invalid track: {track}") + results[idx] = None + return [r for r in results if r] + def filter_results(self, results: list, phrase: str, lang: str, + media_type: MediaType = MediaType.GENERIC) -> list: # ignore very low score matches l1 = len(results) results = [r for r in results @@ -808,9 +838,15 @@ def _search(self, phrase: str, media_type: MediaType, lang: str, skills=skills): results += r["results"] - LOG.debug(f"Got {len(results)} results") - results = self.filter_results(results, phrase, lang, media_type) - LOG.debug(f"Got {len(results)} usable results") + results = self.normalize_results(results) + + if not skills: + LOG.debug(f"Got {len(results)} results") + results = self.filter_results(results, phrase, lang, media_type) + LOG.debug(f"Got {len(results)} usable results") + else: # no filtering if skill explicitly requested + LOG.debug(f"Got {len(results)} usable results from {skills}") + self.bus.emit(Message("ovos.common_play.search.end")) return results @@ -865,7 +901,7 @@ def select_best(self, results: list) -> MediaEntry: for res in results: if isinstance(res, dict): - res = MediaEntry.from_dict(res) + res = dict2entry(res) if not best or res.match_confidence > best.match_confidence: best = res ties = [best]