Merge branch 'forslund-feature/simple-audio' into dev
commit
2c7c51cea4
|
@ -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()
|
||||
|
|
|
@ -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):
|
||||
|
|
|
@ -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
|
|
@ -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
|
|
@ -291,11 +291,7 @@
|
|||
"Audio": {
|
||||
"backends": {
|
||||
"local": {
|
||||
"type": "mpg123",
|
||||
"active": true
|
||||
},
|
||||
"ogg": {
|
||||
"type": "ogg123",
|
||||
"type": "simple",
|
||||
"active": true
|
||||
},
|
||||
"vlc": {
|
||||
|
|
|
@ -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):
|
||||
'''
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
Loading…
Reference in New Issue