Skip to content

Commit

Permalink
ovos-media-plugin-simple (#6)
Browse files Browse the repository at this point in the history
* bump OPM

needs OpenVoiceOS/ovos-plugin-manager#207

* 🎉

latest OCP

.

---------

Co-authored-by: JarbasAi <[email protected]>
  • Loading branch information
NeonJarbas and JarbasAl authored Feb 8, 2024
1 parent 5fc613d commit a19edd4
Show file tree
Hide file tree
Showing 15 changed files with 152 additions and 113 deletions.
4 changes: 2 additions & 2 deletions .github/workflows/build_tests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -7,15 +7,15 @@ on:
branches:
- dev
paths-ignore:
- 'ovos_audio_plugin_simple/version.py'
- 'ovos_media_plugin_simple/version.py'
- 'test/**'
- 'examples/**'
- '.github/**'
- '.gitignore'
- 'LICENSE'
- 'CHANGELOG.md'
- 'MANIFEST.in'
- 'readme.md'
- 'README.md'
- 'scripts/**'
workflow_dispatch:

Expand Down
4 changes: 2 additions & 2 deletions .github/workflows/publish_alpha.yml
Original file line number Diff line number Diff line change
Expand Up @@ -6,15 +6,15 @@ on:
branches:
- dev
paths-ignore:
- 'ovos_audio_plugin_simple/version.py'
- 'ovos_media_plugin_simple/version.py'
- 'test/**'
- 'examples/**'
- '.github/**'
- '.gitignore'
- 'LICENSE'
- 'CHANGELOG.md'
- 'MANIFEST.in'
- 'readme.md'
- 'README.md'
- 'scripts/**'
workflow_dispatch:

Expand Down
10 changes: 5 additions & 5 deletions .github/workflows/unit_tests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -4,29 +4,29 @@ on:
branches:
- dev
paths-ignore:
- 'ovos_audio_plugin_simple/version.py'
- 'ovos_media_plugin_simple/version.py'
- 'requirements/**'
- 'examples/**'
- '.github/**'
- '.gitignore'
- 'LICENSE'
- 'CHANGELOG.md'
- 'MANIFEST.in'
- 'readme.md'
- 'README.md'
- 'scripts/**'
push:
branches:
- master
paths-ignore:
- 'ovos_audio_plugin_simple/version.py'
- 'ovos_media_plugin_simple/version.py'
- 'requirements/**'
- 'examples/**'
- '.github/**'
- '.gitignore'
- 'LICENSE'
- 'CHANGELOG.md'
- 'MANIFEST.in'
- 'readme.md'
- 'README.md'
- 'scripts/**'
workflow_dispatch:

Expand Down Expand Up @@ -56,7 +56,7 @@ jobs:
pip install pytest pytest-timeout pytest-cov
- name: Run unittests
run: |
pytest --cov=ovos_audio_plugin_simple --cov-report xml test/unittests
pytest --cov=ovos_media_plugin_simple --cov-report xml test/unittests
# NOTE: additional pytest invocations should also add the --cov-append flag
# or they will overwrite previous invocations' coverage reports
# (for an example, see OVOS Skill Manager's workflow)
Expand Down
2 changes: 1 addition & 1 deletion MANIFEST.in
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
recursive-include ovos_audio_plugin_simple/ *
recursive-include ovos_media_plugin_simple/ *
recursive-include requirements/ *
include CHANGELOG.md
include LICENSE
37 changes: 37 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
# ovos-media-plugin-simple

simple plugin for [ovos-media](https://github.com/OpenVoiceOS/ovos-media)

tries to use the best command line utility available to play audio, `sox` is recommended

## Install

`pip install ovos-media-plugin-simple`

## Configuration


```javascript
{
"media": {

// keys are the strings defined in "audio_players"
"preferred_audio_services": ["mplayer", "vlc", "simple"],

// PlaybackType.AUDIO handlers
"audio_players": {
// simple player uses a slave simple instance to handle uris
"simple": {
// the plugin name
"module": "ovos-media-audio-plugin-simple",

// users may request specific handlers in the utterance
// using these aliases
"aliases": ["Simple Player", "Command line"],

// deactivate a plugin by setting to false
"active": true
}
}
}
```
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,11 @@
import re
import signal
import subprocess
import time
from distutils.spawn import find_executable
from time import sleep

from ovos_plugin_common_play.ocp.base import OCPAudioPlayerBackend
from ovos_plugin_common_play.ocp.status import TrackState, MediaState, PlayerState
from ovos_plugin_manager.templates.audio import AudioBackend
from ovos_bus_client import Message
from ovos_plugin_manager.templates.media import AudioPlayerBackend
from ovos_utils.log import LOG
from requests import Session

Expand All @@ -31,7 +29,7 @@ def find_mime(path):
return (None, None)


def play_audio(uri, play_cmd="play"):
def play_audio(uri, play_cmd):
""" Play a audio file.
Returns: subprocess.Popen object
Expand All @@ -46,33 +44,43 @@ def play_audio(uri, play_cmd="play"):
return None


SimpleAudioPluginConfig = {
"simple": {
"type": "ovos_simple",
"active": True
}
}


class OVOSSimpleService(OCPAudioPlayerBackend):
class SimpleAudioService(AudioPlayerBackend):
sox_play = find_executable("play")
pulse_play = find_executable("paplay")
alsa_play = find_executable("aplay")
mpg123_play = find_executable("mpg123")

def __init__(self, config, bus=None, name='ovos_simple'):
super(OVOSSimpleService, self).__init__(config, bus)
self.name = name

def __init__(self, config, bus=None):
super().__init__(config, bus)
self.process = None
self._stop_signal = False
self._is_playing = False
self._paused = False
self.ts = 0

self.supports_mime_hints = True
mimetypes.init()

self.bus.on('ovos.common_play.simple.play', self._play)
def on_track_start(self):
self.ts = time.time()
# Indicate to audio service which track is being played
if self._track_start_callback:
self._track_start_callback(self._now_playing)

def on_track_end(self):
self._is_playing = False
self._paused = False
self.process = None
self.ts = 0
if self._track_start_callback:
self._track_start_callback(None)

def on_track_error(self):
self._is_playing = False
self._paused = False
self.process = None
self.ts = 0
self.ocp_error()

# simple player internals
def _get_track(self, track_data):
Expand Down Expand Up @@ -105,21 +113,9 @@ def _stop_running_process(self):
self.process.kill()
self.process = None

def _play(self, message):
"""Implementation specific async method to handle playback.
This allows mpg123 service to use the next method as well
as basic play/stop.
"""
LOG.info('SimpleAudioService._play')

# Stop any existing audio playback
self._stop_running_process()

repeat = message.data.get('repeat', False)
self._is_playing = True
self._paused = False

@property
def player_cmd(self):
"""determine the best command to play a stream"""
# sox should handle almost every format, but fails in some urls
if self.sox_play:
track = self._now_playing
Expand All @@ -143,53 +139,46 @@ def _play(self, message):

# fallback to alsa, only wav files will play correctly
player = player or self.alsa_play
return player

# Indicate to audio service which track is being played
self._track_start_callback(track)
# audio service
def supported_uris(self):
uris = ['file', 'http']
if self.sox_play:
uris.append("https")
return uris

def play(self, repeat=False):
""" Play playlist using simple. """
# Stop any existing audio playback
self._stop_running_process()

self._is_playing = True
self._paused = False

# Replace file:// uri's with normal paths
uri = track.replace('file://', '')
uri = self._now_playing.replace('file://', '')

self.on_track_start()
try:
self.process = play_audio(uri, player)
self.process = play_audio(uri, self.player_cmd)
except FileNotFoundError as e:
LOG.error(f'Couldn\'t play audio, {e}')
self.process = None
self.ocp_error()
self.on_track_error()
except Exception as e:
LOG.exception(repr(e))
self.process = None
self.ocp_error()
self.on_track_error()

# Wait for completion or stop request
while (self._is_process_running() and not self._stop_signal):
sleep(0.25)

if self._stop_signal:
self._stop_running_process()
self._is_playing = False
self._paused = False
return
else:
self.process = None

self._track_start_callback(None)
self._is_playing = False
self._paused = False
self.ocp_stop()

# audio service
def supported_uris(self):
uris = ['file', 'http']
if self.sox_play:
uris.append("https")
return uris

def play(self, repeat=False):
""" Play playlist using simple. """
self.ocp_start()
self.bus.emit(Message('ovos.common_play.simple.play',
{'repeat': repeat}))
self.on_track_end()

def stop(self):
""" Stop simple playback. """
Expand All @@ -199,7 +188,6 @@ def stop(self):
while self._is_playing:
sleep(0.1)
self._stop_signal = False
self.ocp_stop()
return True
return False

Expand All @@ -209,33 +197,51 @@ def pause(self):
# Suspend the playback process
self.process.send_signal(signal.SIGSTOP)
self._paused = True
self.ocp_pause()

def resume(self):
""" Resume paused playback. """
if self.process and self._paused:
# Resume the playback process
self.process.send_signal(signal.SIGCONT)
self._paused = False
self.ocp_resume()

def track_info(self):
""" Extract info of current track. """
return {"track": self._now_playing}
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.
"""
# Not available in this plugin

def load_service(base_config, bus):
backends = base_config.get('backends', [])
services = [(b, backends[b]) for b in backends
if backends[b]['type'] in ["simple", 'ovos_simple'] and
backends[b].get('active', False)]
def restore_volume(self):
"""Restore normal volume.
if not any([OVOSSimpleService.sox_play,
OVOSSimpleService.pulse_play,
OVOSSimpleService.alsa_play,
OVOSSimpleService.mpg123_play]):
LOG.error("No basic playback functionality detected!!")
return []
Called when to restore the playback volume to previous level after
OpenVoiceOS has lowered it using lower_volume().
"""
# Not available in this plugin

instances = [OVOSSimpleService(s[1], bus, s[0]) for s in services]
return instances
def get_track_length(self) -> int:
"""
getting the duration of the audio in milliseconds
"""
# we only can estimate how much we already played as a minimum value
return self.get_track_position()

def get_track_position(self) -> int:
"""
get current position in milliseconds
"""
# approximate given timestamp of playback start
if self.ts:
return int((time.time() - self.ts) * 1000)
return 0

def set_track_position(self, milliseconds):
"""
go to position in milliseconds
Args:
milliseconds (int): number of milliseconds of final position
"""
# Not available in this plugin
File renamed without changes.
3 changes: 1 addition & 2 deletions requirements/requirements.txt
Original file line number Diff line number Diff line change
@@ -1,2 +1 @@
ovos-plugin-manager
ovos_plugin_common_play
ovos-plugin-manager>=0.0.26a5
Loading

0 comments on commit a19edd4

Please sign in to comment.