Skip to content

Commit

Permalink
feat/OCP_backends
Browse files Browse the repository at this point in the history
  • Loading branch information
JarbasAl committed Jan 8, 2024
1 parent 5f079a1 commit 39ef413
Showing 1 changed file with 277 additions and 0 deletions.
277 changes: 277 additions & 0 deletions ovos_plugin_manager/templates/media.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,277 @@
from abc import ABCMeta, abstractmethod

from ovos_bus_client.message import Message
from ovos_utils.log import LOG
from ovos_utils.messagebus import FakeBus

from ovos_utils.ocp import MediaState, PlayerState, TrackState


class MediaBackend(metaclass=ABCMeta):
"""Base class for all OCP media backend implementations.
Media backends are single-track, playlists are handled by OCP
Arguments:
config (dict): configuration dict for the instance
bus (MessageBusClient): Mycroft messagebus emitter
"""

def __init__(self, config=None, bus=None):
self._now_playing = None # single uri
self._track_start_callback = None
self.supports_mime_hints = False
self.config = config or {}
self.bus = bus or FakeBus()

def set_track_start_callback(self, callback_func):
"""Register callback on track start.
This method should be called as each track in a playlist is started.
"""
self._track_start_callback = callback_func

def load_track(self, uri):
self._now_playing = uri
LOG.debug(f"queuing for {self.__class__.__name__} playback: {uri}")
self.bus.emit(Message("ovos.common_play.media.state",
{"state": MediaState.LOADED_MEDIA}))

def ocp_start(self):
"""Emit OCP status events for play"""
self.bus.emit(Message("ovos.common_play.player.state",
{"state": PlayerState.PLAYING}))
self.bus.emit(Message("ovos.common_play.media.state",
{"state": MediaState.LOADED_MEDIA}))
self.play()

def ocp_error(self):
"""Emit OCP status events for playback error"""
if self._now_playing:
self._now_playing = None
self.bus.emit(Message("ovos.common_play.media.state",
{"state": MediaState.INVALID_MEDIA}))
self.bus.emit(Message("ovos.common_play.player.state",
{"state": PlayerState.STOPPED}))

def ocp_stop(self):
"""Emit OCP status events for stop"""
if self._now_playing:
self._now_playing = None
self.bus.emit(Message("ovos.common_play.player.state",
{"state": PlayerState.STOPPED}))
self.bus.emit(Message("ovos.common_play.media.state",
{"state": MediaState.END_OF_MEDIA}))
self.stop()

def ocp_pause(self):
"""Emit OCP status events for pause"""
if self._now_playing:
self.bus.emit(Message("ovos.common_play.player.state",
{"state": PlayerState.PAUSED}))
self.pause()

def ocp_resume(self):
"""Emit OCP status events for resume"""
if self._now_playing:
self.bus.emit(Message("ovos.common_play.player.state",
{"state": PlayerState.PLAYING}))
self.bus.emit(Message("ovos.common_play.track.state",
{"state": TrackState.PLAYING_AUDIO}))
self.resume()

@property
def playback_time(self):
return 0

@abstractmethod
def supported_uris(self):
"""List of supported uri types.
Returns:
list: Supported uri's
"""

@abstractmethod
def play(self):
"""Start playback.
Starts playing the first track in the playlist and will contiune
until all tracks have been played.
"""

@abstractmethod
def stop(self):
"""Stop playback.
Stops the current playback.
Returns:
bool: True if playback was stopped, otherwise False
"""

@abstractmethod
def pause(self):
"""Pause playback.
Stops playback but may be resumed at the exact position the pause
occured.
"""

@abstractmethod
def resume(self):
"""Resume paused playback.
Resumes playback after being paused.
"""

@abstractmethod
def lower_volume(self):
"""Lower volume.
This method is used to implement audio ducking. It will be called when
OpenVoiceOS is listening or speaking to make sure the media playing isn't
interfering.
"""

@abstractmethod
def restore_volume(self):
"""Restore normal volume.
Called when to restore the playback volume to previous level after
OpenVoiceOS has lowered it using lower_volume().
"""

@abstractmethod
def get_track_length(self) -> int:
"""
getting the duration of the audio in milliseconds
"""

@abstractmethod
def get_track_position(self) -> int:
"""
get current position in milliseconds
"""

@abstractmethod
def set_track_position(self, milliseconds):
"""
go to position in milliseconds
Args:
milliseconds (int): number of milliseconds of final position
"""

def seek_forward(self, seconds=1):
"""Skip X seconds.
Arguments:
seconds (int): number of seconds to seek, if negative rewind
"""
miliseconds = seconds * 1000
new_pos = self.get_track_position() + miliseconds
self.set_track_position(new_pos)

def seek_backward(self, seconds=1):
"""Rewind X seconds.
Arguments:
seconds (int): number of seconds to seek, if negative jump forward.
"""
miliseconds = seconds * 1000
new_pos = self.get_track_position() - miliseconds
self.set_track_position(new_pos)

def track_info(self):
"""Get info about current playing track.
Returns:
dict: Track info containing atleast the keys artist and album.
"""
ret = {}
ret['artist'] = ''
ret['album'] = ''
ret['title'] = self._now_playing
return ret

def shutdown(self):
"""Perform clean shutdown.
Implements any audio backend specific shutdown procedures.
"""
self.stop()


class AudioBackend(MediaBackend):
""" for audio"""

def load_track(self, uri):
super().load_track(uri)
self.bus.emit(Message("ovos.common_play.track.state",
{"state": TrackState.QUEUED_AUDIO}))

def ocp_start(self):
"""Emit OCP status events for play"""
super().ocp_start()
self.bus.emit(Message("ovos.common_play.track.state",
{"state": TrackState.PLAYING_AUDIO}))


class RemoteAudioBackend(AudioBackend):
"""Base class for remote audio backends.
RemoteAudioBackends will always be checked after the normal
AudioBackends to make playback start locally by default.
An example of a RemoteAudioBackend would be things like mopidy servers, etc.
"""


class VideoBackend(MediaBackend):
""" for audio"""
def load_track(self, uri):
super().load_track(uri)
self.bus.emit(Message("ovos.common_play.track.state",
{"state": TrackState.QUEUED_VIDEO}))

def ocp_start(self):
"""Emit OCP status events for play"""
super().ocp_start()
self.bus.emit(Message("ovos.common_play.track.state",
{"state": TrackState.PLAYING_VIDEO}))


class RemoteVideoBackend(VideoBackend):
"""Base class for remote audio backends.
RemoteAudioBackends will always be checked after the normal
VideoBackends to make playback start locally by default.
An example of a RemoteVideoBackend would be things like Chromecasts, etc.
"""


class VideoWebBackend(MediaBackend):
""" for web pages"""

def load_track(self, uri):
super().load_track(uri)
self.bus.emit(Message("ovos.common_play.track.state",
{"state": TrackState.QUEUED_WEBVIEW}))

def ocp_start(self):
"""Emit OCP status events for play"""
super().ocp_start()
self.bus.emit(Message("ovos.common_play.track.state",
{"state": TrackState.PLAYING_WEBVIEW}))


class RemoteWebBackend(VideoBackend):
"""Base class for remote web backends.
RemoteAudioBackends will always be checked after the normal
VideoBackends to make playback start locally by default.
An example of a RemoteVideoBackend would be
things that can render a webpage in a different machine
"""

0 comments on commit 39ef413

Please sign in to comment.