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

ovos-media-plugin-simple #6

Merged
merged 2 commits into from
Feb 8, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
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
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
Loading