Merge branch 'forslund-feature/simple-audio' into dev

pull/1770/head
Steve Penrod 2018-08-27 14:34:09 -05:00
commit 2c7c51cea4
8 changed files with 115 additions and 170 deletions

View File

@ -344,7 +344,12 @@ class AudioService(object):
the tracks.
"""
self._stop()
uri_type = tracks[0].split(':')[0]
if isinstance(tracks[0], str):
uri_type = tracks[0].split(':')[0]
else:
uri_type = tracks[0][0].split(':')[0]
# check if user requested a particular service
if prefered_service and uri_type in prefered_service.supported_uris():
selected_service = prefered_service
@ -362,6 +367,8 @@ class AudioService(object):
else:
LOG.info('No service found for uri_type: ' + uri_type)
return
if not selected_service.supports_mime_hints:
tracks = [t[0] if isinstance(t, list) else t for t in tracks]
selected_service.clear_list()
selected_service.add_list(tracks)
selected_service.play()

View File

@ -27,6 +27,7 @@ class AudioBackend():
def __init__(self, config, bus):
self._track_start_callback = None
self.supports_mime_hints = False
@abstractmethod
def supported_uris(self):

View File

@ -1,127 +0,0 @@
# Copyright 2017 Mycroft AI Inc.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#
import subprocess
from time import sleep
from mycroft.audio.services import AudioBackend
from mycroft.messagebus.message import Message
from mycroft.util.log import LOG
class Ogg123Service(AudioBackend):
"""
Audio backend for ogg123 player. This one is rather limited and
only implements basic usage.
"""
def __init__(self, config, bus, name='ogg123'):
super(Ogg123Service, self).__init__(config, bus)
self.config = config
self.process = None
self.bus = bus
self.name = name
self._stop_signal = False
self._is_playing = False
self.index = 0
self.tracks = []
self.bus.on('Ogg123ServicePlay', self._play)
def supported_uris(self):
return ['file', 'http']
def clear_list(self):
self.tracks = []
def add_list(self, tracks):
self.tracks += tracks
LOG.info("Track list is " + str(tracks))
def _play(self, message=None):
""" Implementation specific async method to handle playback.
This allows ogg123 service to use the "next method as well
as basic play/stop.
"""
LOG.info('Ogg123Service._play')
self._is_playing = True
track = self.tracks[self.index]
# Indicate to audio service which track is being played
if self._track_start_callback:
self._track_start_callback(track)
# Replace file:// uri's with normal paths
track = track.replace('file://', '')
self.process = subprocess.Popen(['ogg123', track])
# Wait for completion or stop request
while self.process.poll() is None and not self._stop_signal:
sleep(0.25)
if self._stop_signal:
self.process.terminate()
self.process = None
self._is_playing = False
return
self.index += 1
# if there are more tracks available play next
if self.index < len(self.tracks):
self.bus.emit(Message('Ogg123ServicePlay'))
else:
self._is_playing = False
def play(self):
LOG.info('Call Ogg123ServicePlay')
self.index = 0
self.bus.emit(Message('Ogg123ServicePlay'))
def stop(self):
LOG.info('Ogg123ServiceStop')
if self._is_playing:
self._stop_signal = True
while self._is_playing:
sleep(0.1)
self._stop_signal = False
return True
else:
return False
def pause(self):
pass
def resume(self):
pass
def next(self):
# Terminate process to continue to next
self.process.terminate()
def previous(self):
pass
def lower_volume(self):
pass
def restore_volume(self):
pass
def load_service(base_config, bus):
backends = base_config.get('backends', [])
services = [(b, backends[b]) for b in backends
if backends[b]['type'] == 'ogg123' and
backends[b].get('active', True)]
instances = [Ogg123Service(s[1], bus, s[0]) for s in services]
return instances

View File

@ -18,24 +18,46 @@ from time import sleep
from mycroft.audio.services import AudioBackend
from mycroft.messagebus.message import Message
from mycroft.util.log import LOG
import mimetypes
from requests import Session
class Mpg123Service(AudioBackend):
def find_mime(path):
mime = None
if path.startswith('http'):
response = Session().head(path, allow_redirects=True)
if 200 <= response.status_code < 300:
mime = response.headers['content-type']
if not mime:
mime = mimetypes.guess_mime(path)[0]
if mime:
return mime.split('/')
else:
return (None, None)
class SimpleAudioService(AudioBackend):
"""
Audio backend for mpg123 player. This one is rather limited and
only implements basic usage.
Simple Audio backend for both mpg123 and the ogg123 player.
This one is rather limited and only implements basic usage.
"""
def __init__(self, config, bus, name='mpg123'):
super(Mpg123Service, self).__init__(config, bus)
def __init__(self, config, bus, name='simple'):
super(SimpleAudioService, self).__init__(config, bus)
self.config = config
self.process = None
self.bus = bus
self.name = name
self._stop_signal = False
self._is_playing = False
self.tracks = []
self.index = 0
self.supports_mime_hints = True
mimetypes.init()
bus.on('Mpg123ServicePlay', self._play)
self.bus.on('SimpleAudioServicePlay', self._play)
def supported_uris(self):
return ['file', 'http']
@ -52,20 +74,36 @@ class Mpg123Service(AudioBackend):
This allows mpg123 service to use the "next method as well
as basic play/stop.
"""
LOG.info('Mpg123Service._play')
LOG.info('SimpleAudioService._play')
self._is_playing = True
track = self.tracks[self.index]
if isinstance(self.tracks[self.index], list):
track = self.tracks[self.index][0]
mime = self.tracks[self.index][1]
mime = mime.split('/')
else: # Assume string
track = self.tracks[self.index]
mime = find_mime(track)
print('MIME: ' + str(mime))
# Indicate to audio service which track is being played
if self._track_start_callback:
self._track_start_callback(track)
# Replace file:// uri's with normal paths
track = track.replace('file://', '')
self.process = subprocess.Popen(['mpg123', track])
# Wait for completion or stop request
while self.process.poll() is None and not self._stop_signal:
sleep(0.25)
proc = None
if 'mpeg' in mime[1]:
proc = 'mpg123'
elif 'ogg' in mime[1]:
proc = 'ogg123'
elif 'wav' in mime[1]:
proc = 'aplay'
else:
proc = 'mpg123' # If no mime info could be determined gues mp3
if proc:
self.process = subprocess.Popen([proc, track])
# Wait for completion or stop request
while self.process.poll() is None and not self._stop_signal:
sleep(0.25)
if self._stop_signal:
self.process.terminate()
@ -76,25 +114,21 @@ class Mpg123Service(AudioBackend):
self.index += 1
# if there are more tracks available play next
if self.index < len(self.tracks):
self.bus.emit(Message('Mpg123ServicePlay'))
self.bus.emit(Message('SimpleAudioServicePlay'))
else:
self._is_playing = False
def play(self):
LOG.info('Call Mpg123ServicePlay')
LOG.info('Call SimpleAudioServicePlay')
self.index = 0
self.bus.emit(Message('Mpg123ServicePlay'))
self.bus.emit(Message('SimpleAudioServicePlay'))
def stop(self):
LOG.info('Mpg123ServiceStop')
if self._is_playing:
self._stop_signal = True
while self._is_playing:
sleep(0.01)
self._stop_signal = False
return True
else:
return False
LOG.info('SimpleAudioServiceStop')
self._stop_signal = True
while self._is_playing:
sleep(0.1)
self._stop_signal = False
def pause(self):
pass
@ -119,7 +153,7 @@ class Mpg123Service(AudioBackend):
def load_service(base_config, bus):
backends = base_config.get('backends', [])
services = [(b, backends[b]) for b in backends
if backends[b]['type'] == 'mpg123' and
if backends[b]['type'] == 'simple' and
backends[b].get('active', True)]
instances = [Mpg123Service(s[1], bus, s[0]) for s in services]
instances = [SimpleAudioService(s[1], bus, s[0]) for s in services]
return instances

View File

@ -291,11 +291,7 @@
"Audio": {
"backends": {
"local": {
"type": "mpg123",
"active": true
},
"ogg": {
"type": "ogg123",
"type": "simple",
"active": true
},
"vlc": {

View File

@ -16,6 +16,7 @@ import json
import time
from multiprocessing.pool import ThreadPool
from threading import Event
import traceback
from pyee import EventEmitter
from websocket import (WebSocketApp, WebSocketConnectionClosedException,
@ -144,7 +145,7 @@ class WebsocketClient(object):
# ValueError occurs on pyee 1.0.1 removing handlers
# registered with once.
# KeyError may theoretically occur if the event occurs as
# the handler is removbed
# the handler is removed
pass
return None
return response[0]
@ -157,9 +158,31 @@ class WebsocketClient(object):
def remove(self, event_name, func):
try:
if event_name in self.emitter._events:
LOG.debug("Removing found '"+str(event_name)+"'")
else:
LOG.debug("Not able to find '"+str(event_name)+"'")
self.emitter.remove_listener(event_name, func)
except ValueError as e:
LOG.warning('Failed to remove event {}: {}'.format(event_name, e))
import traceback
LOG.warning('Failed to remove event {}: {}'.format(event_name, str(func), e))
for line in traceback.format_stack():
LOG.warning(line.strip())
if event_name in self.emitter._events:
LOG.debug("Removing found '"+str(event_name)+"'")
else:
LOG.debug("Not able to find '"+str(event_name)+"'")
LOG.warning("Existing events: " + str(self.emitter._events))
for evt in self.emitter._events:
LOG.warning(" "+str(evt))
LOG.warning(" "+str(self.emitter._events[evt]))
if event_name in self.emitter._events:
LOG.debug("Removing found '"+str(event_name)+"'")
else:
LOG.debug("Not able to find '"+str(event_name)+"'")
LOG.warning('----- End dump -----')
def remove_all_listeners(self, event_name):
'''

View File

@ -28,10 +28,18 @@ def ensure_uri(s):
Returns:
if s is uri, s is returned otherwise file:// is prepended
"""
if '://' not in s:
return 'file://' + abspath(s)
if isinstance(s, str):
if '://' not in s:
return 'file://' + abspath(s)
else:
return s
elif isinstance(s, (tuple, list)):
if '://' not in s[0]:
return 'file://' + abspath(s[0]), s[1]
else:
return s
else:
return s
raise ValueError('Invalid track')
class AudioService(object):
@ -74,11 +82,13 @@ class AudioService(object):
Args:
tracks: track uri or list of track uri's
Each track can be added as a tuple with (uri, mime)
to give a hint of the mime type to the system
utterance: forward utterance for further processing by the
audio service.
"""
tracks = tracks or []
if isinstance(tracks, str):
if isinstance(tracks, (str, tuple)):
tracks = [tracks]
elif not isinstance(tracks, list):
raise ValueError

View File

@ -16,8 +16,9 @@ from mycroft.audio.services import AudioBackend
class WorkingBackend(AudioBackend):
def __init__(self, config, emitter, name='Working'):
pass
def __init__(self, config, bus, name='Working'):
super(WorkingBackend, self).__init__(config, bus)
self.name = name
def supported_uris(self):
return ['file', 'http']
@ -35,6 +36,6 @@ class WorkingBackend(AudioBackend):
pass
def load_service(base_config, emitter):
instances = [WorkingBackend(base_config, emitter)]
def load_service(base_config, bus):
instances = [WorkingBackend(base_config, bus)]
return instances