Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Bugfixes and Unit Tests #69

Merged
merged 22 commits into from
Apr 8, 2023
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
Show all changes
22 commits
Select commit Hold shift + click to select a range
28d2d88
Resolve error relating to #65
NeonDaniel Apr 5, 2023
3c44115
Update logging and docstrings
NeonDaniel Apr 6, 2023
9bd64d3
More unit tests and documentation/code cleanup of player playback met…
NeonDaniel Apr 6, 2023
b572e22
Add tests and documentation for pause, resume, seek, and reset methods
NeonDaniel Apr 6, 2023
351b807
Update to handle empty URI per PR feedback
NeonDaniel Apr 6, 2023
3ed9786
Resolving unit test failures
NeonDaniel Apr 6, 2023
55fdc1b
Resolving unit test failures
NeonDaniel Apr 6, 2023
cc153cd
Refactor Message arg per review
NeonDaniel Apr 6, 2023
b62aa78
Add tests for `handle_player_state_update` with handling of int state…
NeonDaniel Apr 7, 2023
3435030
Add tests for `play_next` with updated comments and logging
NeonDaniel Apr 7, 2023
38f5b3f
Cleanup and fix bugs in `handle_player_media_update` with unit test
NeonDaniel Apr 7, 2023
a0a4acc
Revert equality check per PR review
NeonDaniel Apr 7, 2023
fda0473
Add MediaEntry and Playlist tests and docstrings
NeonDaniel Apr 7, 2023
10863ba
Add MediaEntry and Playlist tests
NeonDaniel Apr 7, 2023
4191974
Complete `media` tests and docstrings
NeonDaniel Apr 7, 2023
ed55658
Ensure MediaEntry.playback is PlaybackType with unit tests
NeonDaniel Apr 7, 2023
b3e738c
Fix MessageBusClient import location
NeonDaniel Apr 7, 2023
a611375
Add logging to troubleshoot playback errors
NeonDaniel Apr 7, 2023
aef12d2
Update logging to troubleshoot
NeonDaniel Apr 7, 2023
434a28a
Refactor `NowPlaying` reset and update unit tests
NeonDaniel Apr 8, 2023
f2d1981
Update test to handle refactored reset calls
NeonDaniel Apr 8, 2023
17a1043
Update 'play_next' based on PR conversation
NeonDaniel Apr 8, 2023
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
131 changes: 110 additions & 21 deletions ovos_plugin_common_play/ocp/media.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
from typing import Optional, Tuple, List, Union

from mycroft.messagebus import MessageBusClient
NeonDaniel marked this conversation as resolved.
Show resolved Hide resolved
from ovos_plugin_common_play.ocp import OCP_ID
from ovos_plugin_common_play.ocp.status import *
from ovos_plugin_common_play.ocp.utils import ocp_plugins, find_mime
Expand Down Expand Up @@ -342,18 +343,36 @@ def __contains__(self, item):


class NowPlaying(MediaEntry):
def __init__(self, *args, **kwargs):
MediaEntry.__init__(self, *args, **kwargs)
self._player = None

@property
def bus(self):
def bus(self) -> MessageBusClient:
"""
Return the MessageBusClient inherited from the bound OCPMediaPlayer
"""
return self._player.bus

@property
def _settings(self):
def _settings(self) -> dict:
"""
Return the dict settings inherited from the bound OCPMediaPlayer
"""
return self._player.settings

def as_entry(self):
def as_entry(self) -> MediaEntry:
"""
Return a MediaEntry representation of this object
"""
return MediaEntry.from_dict(self.as_dict)

def bind(self, player):
"""
Bind an OCPMediaPlayer object to this NowPlaying instance. Registers
messagebus event handlers and defines `self._player`
@param player: OCPMediaPlayer instance to bind
"""
# needs to start with _ to avoid json serialization errors
self._player = player
self._player.add_event("ovos.common_play.track.state",
Expand All @@ -374,12 +393,18 @@ def bind(self, player):
self.handle_audio_service_play_start)

def shutdown(self):
"""
Remove NowPlaying events from the MessageBusClient
"""
self._player.remove_event("ovos.common_play.track.state")
self._player.remove_event("ovos.common_play.playback_time")
self._player.remove_event('gui.player.media.service.get.meta')
self._player.remove_event('mycroft.audio_only.service.track_info_reply')

def reset(self):
"""
Reset the NowPlaying MediaEntry to default parameters
"""
self.title = ""
self.artist = None
self.skill_icon = None
Expand All @@ -394,18 +419,33 @@ def reset(self):
self.playback = PlaybackType.UNDEFINED
self.status = TrackState.DISAMBIGUATION

def update(self, entry, skipkeys=None, newonly=False):
def update(self, entry: dict, skipkeys: list = None, newonly: bool = False):
"""
Update this MediaEntry and emit `gui.player.media.service.set.meta`
@param entry: dict or MediaEntry object to update this object with
@param skipkeys: list of keys to not change
@param newonly: if True, only adds new keys; existing keys are unchanged
"""
if isinstance(entry, MediaEntry):
entry = entry.as_dict
super().update(entry, skipkeys, newonly)
# uri updates should not be skipped
if newonly and entry.get("uri"):
super().update({"uri": entry["uri"]})
# sync with gui media player on track change
if not self._player:
LOG.error("Instance not bound! Call `bind` before trying to use "
"the messagebus.")
return
self.bus.emit(Message("gui.player.media.service.set.meta",
{"title": self.title,
"image": self.image,
"artist": self.artist}))

def extract_stream(self):
"""
Get metadata from ocp_plugins and add it to this MediaEntry
"""
uri = self.uri
if not uri:
raise ValueError("No URI to extract stream from")
Expand All @@ -416,15 +456,19 @@ def extract_stream(self):
meta = ocp_plugins.extract_stream(uri, video)
# update media entry with new data
if meta:
LOG.debug(f"OCP plugins metadata: {meta}")
LOG.info(f"OCP plugins metadata: {meta}")
self.update(meta, newonly=True)
elif not any((uri.startswith(s) for s in ["http", "file", "/"])):
LOG.info(f"OCP WARNING: plugins returned no metadata for uri {uri}")

# events from gui_player/audio_service
def handle_external_play(self, message):
# update metadata unconditionally
# otherwise previous song keys might bleed into new track
"""
Handle 'ovos.common_play.play' Messages. Update the metadata with new
data received unconditionally, otherwise previous song keys might
bleed into the new track
@param message: Message associated with request
"""
if message.data.get("tracks"):
# backwards compat / old style
playlist = message.data["tracks"]
Expand All @@ -435,55 +479,96 @@ def handle_external_play(self, message):
self.update(media, newonly=False)

def handle_player_metadata_request(self, message):
"""
Handle 'gui.player.media.service.get.meta' Messages. Emit a response for
the GUI to handle new metadata.
@param message: Message associated with request
"""
self.bus.emit(message.reply("gui.player.media.service.set.meta",
{"title": self.title,
"image": self.image,
"artist": self.artist}))

def handle_track_state_change(self, message):
status = message.data["state"]
self.status = status
for k in TrackState:
if k == status:
LOG.info(f"TrackState changed: {repr(k)}")
"""
Handle 'ovos.common_play.track.state' Messages. Update status
@param message: Message with updated `state` data
@return:
"""
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 not isinstance(state, TrackState):
raise ValueError(f"Expected int or TrackState, but got: {state}")

if status == TrackState.PLAYING_SKILL:
if state == self.status:
return
self.status = state
LOG.info(f"TrackState changed: {state}")

if state == TrackState.PLAYING_SKILL:
# skill is handling playback internally
pass
elif status == TrackState.PLAYING_AUDIOSERVICE:
elif state == TrackState.PLAYING_AUDIOSERVICE:
# audio service is handling playback
pass
elif status == TrackState.PLAYING_VIDEO:
elif state == TrackState.PLAYING_VIDEO:
# ovos common play is handling playback in GUI
pass
elif status == TrackState.PLAYING_AUDIO:
elif state == TrackState.PLAYING_AUDIO:
# ovos common play is handling playback in GUI
pass

elif status == TrackState.DISAMBIGUATION:
elif state == TrackState.DISAMBIGUATION:
# alternative results # TODO its this 1 track or a list ?
pass
elif status in [TrackState.QUEUED_SKILL,
elif state in [TrackState.QUEUED_SKILL,
TrackState.QUEUED_VIDEO,
TrackState.QUEUED_AUDIOSERVICE]:
# audio service is handling playback and this is in playlist
pass

def handle_media_state_change(self, message):
status = message.data["state"]
if status == MediaState.END_OF_MEDIA:
"""
Handle 'ovos.common_play.media.state' Messages. If ended, reset.
@param message: Message with updated MediaState
"""
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 = MediaState(state)
if not isinstance(state, MediaState):
raise ValueError(f"Expected int or TrackState, but got: {state}")
if state == MediaState.END_OF_MEDIA:
# playback ended, allow next track to change metadata again
self.reset()

def handle_sync_seekbar(self, message):
""" event sent by ovos audio backend plugins """
"""
Handle 'ovos.common_play.playback_time' Messages sent by audio backend
@param message: Message with 'length' and 'position' data
"""
self.length = message.data["length"]
self.position = message.data["position"]

def handle_sync_trackinfo(self, message):
"""
Handle 'mycroft.audio.service.track_info_reply' Messages with current
media defined in message.data
@param message: Message with dict MediaEntry data
"""
self.update(message.data)

def handle_audio_service_play(self, message):
"""
Handle 'mycroft.audio.service.play' Messages with list of tracks in data
@param message: Message with 'tracks' data
"""
tracks = message.data.get("tracks") or []
# only present in ovos-core
skill_id = message.context.get("skill_id") or 'mycroft.audio_interface'
Expand All @@ -502,6 +587,10 @@ def handle_audio_service_play(self, message):
pass

def handle_audio_service_play_start(self, message):
"""
Handle 'mycroft.audio.playing_track' Messages
@param message: Message notifying playback has started
"""
self.update(
{"status": TrackState.PLAYING_AUDIOSERVICE,
"playback": PlaybackType.AUDIO_SERVICE})
Expand Down
Loading