18 from os
import listdir
19 from os.path
import abspath, dirname, basename, isdir, join
20 from threading
import Lock
26 from .services
import RemoteAudioBackend
33 MAINMODULE =
'__init__' 34 sys.path.append(abspath(dirname(__file__)))
38 """Prepares a descriptor that can be used together with imp. 41 service_folder: folder that shall be imported. 44 Dict with import information 46 info = imp.find_module(MAINMODULE, [service_folder])
47 return {
"name": basename(service_folder),
"info": info}
52 Load and initialize services from all subfolders. 55 services_folder: base folder to look for services in. 58 Sorted list of audio services. 60 LOG.info(
"Loading services from " + services_folder)
62 possible_services = listdir(services_folder)
63 for i
in possible_services:
64 location = join(services_folder, i)
65 if (isdir(location)
and 66 not MAINMODULE +
".py" in listdir(location)):
67 for j
in listdir(location):
68 name = join(location, j)
69 if (
not isdir(name)
or 70 not MAINMODULE +
".py" in listdir(name)):
75 LOG.error(
'Failed to create service from ' + name,
77 if (
not isdir(location)
or 78 not MAINMODULE +
".py" in listdir(location)):
83 LOG.error(
'Failed to create service from ' + location,
85 return sorted(services, key=
lambda p: p.get(
'name'))
90 Search though the service directory and load any services. 93 config: configuration dict for the audio backends. 94 bus: Mycroft messagebus 97 List of started services. 100 path = dirname(abspath(__file__)) +
'/services/' 103 for descriptor
in service_directories:
104 LOG.info(
'Loading ' + descriptor[
'name'])
106 service_module = imp.load_module(descriptor[
"name"] + MAINMODULE,
108 except Exception
as e:
109 LOG.error(
'Failed to import module ' + descriptor[
'name'] +
'\n' +
113 if (hasattr(service_module,
'autodetect')
and 114 callable(service_module.autodetect)):
116 s = service_module.autodetect(config, bus)
118 except Exception
as e:
119 LOG.error(
'Failed to autodetect. ' + repr(e))
120 if hasattr(service_module,
'load_service'):
122 s = service_module.load_service(config, bus)
124 except Exception
as e:
125 LOG.error(
'Failed to load service. ' + repr(e))
131 """ Audio Service class. 132 Handles playback of audio and selecting proper backend for the uri 139 bus: Mycroft messagebus 161 Main callback function for loading services. Sets up the globals 162 service and default and registers the event handlers for the 168 local = [s
for s
in services
if not isinstance(s, RemoteAudioBackend)]
169 remote = [s
for s
in services
if isinstance(s, RemoteAudioBackend)]
177 default_name = self.config.get(
'default-backend',
'')
178 LOG.info(
'Finding default backend...')
180 if s.name == default_name:
182 LOG.info(
'Found ' + self.default.name)
186 LOG.info(
'no default found')
189 self.bus.on(
'mycroft.audio.service.play', self.
_play)
190 self.bus.on(
'mycroft.audio.service.queue', self.
_queue)
191 self.bus.on(
'mycroft.audio.service.pause', self.
_pause)
192 self.bus.on(
'mycroft.audio.service.resume', self.
_resume)
193 self.bus.on(
'mycroft.audio.service.stop', self.
_stop)
194 self.bus.on(
'mycroft.audio.service.next', self.
_next)
195 self.bus.on(
'mycroft.audio.service.prev', self.
_prev)
196 self.bus.on(
'mycroft.audio.service.track_info', self.
_track_info)
197 self.bus.on(
'mycroft.audio.service.list_backends', self.
_list_backends)
198 self.bus.on(
'mycroft.audio.service.seek_forward', self.
_seek_forward)
199 self.bus.on(
'mycroft.audio.service.seek_backward', self.
_seek_backward)
200 self.bus.on(
'recognizer_loop:audio_output_start', self.
_lower_volume)
201 self.bus.on(
'recognizer_loop:record_begin', self.
_lower_volume)
207 Callback method called from the services to indicate start of 210 self.bus.emit(
Message(
'mycroft.audio.playing_track',
211 data={
'track': track}))
215 Handler for mycroft.audio.service.pause. Pauses the current audio 219 message: message bus message, not used but required 226 Handler for mycroft.audio.service.resume. 229 message: message bus message, not used but required 232 self.current.resume()
236 Handler for mycroft.audio.service.next. Skips current track and 237 starts playing the next. 240 message: message bus message, not used but required 247 Handler for mycroft.audio.service.prev. Starts playing the previous 251 message: message bus message, not used but required 254 self.current.previous()
258 Handler for mycroft.stop. Stops any playing service. 261 message: message bus message, not used but required 264 LOG.debug(
'stopping all playing services')
267 name = self.current.name
268 if self.current.stop():
269 self.bus.emit(
Message(
"mycroft.stop.handled",
270 {
"by":
"audio:" + name}))
276 Is triggered when mycroft starts to speak and reduces the volume. 279 message: message bus message, not used but required 282 LOG.debug(
'lowering volume')
283 self.current.lower_volume()
288 except Exception
as exc:
293 Mute all pulse audio input sinks except for the one named 296 for sink
in self.pulse.sink_input_list():
297 if sink.name !=
'mycroft-voice':
298 self.pulse.sink_input_mute(sink.index, 1)
299 self.muted_sinks.append(sink.index)
303 Unmute all pulse audio input sinks. 305 for sink
in self.pulse.sink_input_list():
307 self.pulse.sink_input_mute(sink.index, 0)
312 Lower volume of all pulse audio input sinks except the one named 315 for sink
in self.pulse.sink_input_list():
316 if sink.name !=
'mycroft-voice':
318 volume.value_flat *= 0.3
319 self.pulse.volume_set(sink, volume)
323 Restore volume of all pulse audio input sinks except the one named 326 for sink
in self.pulse.sink_input_list():
327 if sink.name !=
'mycroft-voice':
329 volume.value_flat /= 0.3
330 self.pulse.volume_set(sink, volume)
334 Is triggered when mycroft is done speaking and restores the volume 337 message: message bus message, not used but required 340 LOG.debug(
'restoring volume')
344 self.current.restore_volume()
348 def play(self, tracks, prefered_service, repeat=False):
350 play starts playing the audio on the prefered service if it 351 supports the uri. If not the next best backend is found. 354 tracks: list of tracks to play. 355 repeat: should the playlist repeat 356 prefered_service: indecates the service the user prefer to play 361 if isinstance(tracks[0], str):
362 uri_type = tracks[0].split(
':')[0]
364 uri_type = tracks[0][0].split(
':')[0]
367 if prefered_service
and uri_type
in prefered_service.supported_uris():
368 selected_service = prefered_service
370 elif self.
default and uri_type
in self.default.supported_uris():
371 LOG.debug(
"Using default backend ({})".format(self.default.name))
372 selected_service = self.
default 374 LOG.debug(
"Searching the services")
376 if uri_type
in s.supported_uris():
377 LOG.debug(
"Service {} supports URI {}".format(s, uri_type))
381 LOG.info(
'No service found for uri_type: ' + uri_type)
383 if not selected_service.supports_mime_hints:
384 tracks = [t[0]
if isinstance(t, list)
else t
for t
in tracks]
385 selected_service.clear_list()
386 selected_service.add_list(tracks)
387 selected_service.play(repeat)
388 self.
current = selected_service
393 tracks = message.data[
'tracks']
394 self.current.add_list(tracks)
400 Handler for mycroft.audio.service.play. Starts playback of a 401 tracklist. Also determines if the user requested a special 405 message: message bus message, not used but required 407 tracks = message.data[
'tracks']
408 repeat = message.data.get(
'repeat',
False)
411 if (
'utterance' in message.data
and 412 s.name
in message.data[
'utterance']):
414 LOG.debug(s.name +
' would be prefered')
417 prefered_service =
None 418 self.
play(tracks, prefered_service, repeat)
422 Returns track info on the message bus. 425 message: message bus message, not used but required 428 track_info = self.current.track_info()
431 self.bus.emit(
Message(
'mycroft.audio.service.track_info_reply',
435 """ Return a dict of available backends. """ 439 'supported_uris': s.supported_uris(),
441 'remote': isinstance(s, RemoteAudioBackend)
444 self.bus.emit(message.response(data))
448 Handle message bus command to skip X seconds 451 message: message bus message 453 seconds = message.data.get(
"seconds", 1)
455 self.current.seek_forward(seconds)
459 Handle message bus command to rewind X seconds 462 message: message bus message 464 seconds = message.data.get(
"seconds", 1)
466 self.current.seek_backward(seconds)
470 Select functions for handling lower volume/restore of 471 pulse audio input sinks. 474 pulse_choice: method selection, can be eithe 'mute' or 'lower' 476 if pulsectl
and pulse_choice:
477 self.
pulse = pulsectl.Pulse(
'Mycroft-audio-service')
478 if pulse_choice ==
'mute':
481 elif pulse_choice ==
'lower':
488 LOG.info(
'shutting down ' + s.name)
490 except Exception
as e:
491 LOG.error(
'shutdown of ' + s.name +
' failed: ' + repr(e))
494 self.bus.remove(
'mycroft.audio.service.play', self.
_play)
495 self.bus.remove(
'mycroft.audio.service.queue', self.
_queue)
496 self.bus.remove(
'mycroft.audio.service.pause', self.
_pause)
497 self.bus.remove(
'mycroft.audio.service.resume', self.
_resume)
498 self.bus.remove(
'mycroft.audio.service.stop', self.
_stop)
499 self.bus.remove(
'mycroft.audio.service.next', self.
_next)
500 self.bus.remove(
'mycroft.audio.service.prev', self.
_prev)
501 self.bus.remove(
'mycroft.audio.service.track_info', self.
_track_info)
502 self.bus.remove(
'mycroft.audio.service.seek_forward',
504 self.bus.remove(
'mycroft.audio.service.seek_backward',
506 self.bus.remove(
'recognizer_loop:audio_output_start',
508 self.bus.remove(
'recognizer_loop:record_begin', self.
_lower_volume)
509 self.bus.remove(
'recognizer_loop:audio_output_end',
def _restore_volume(self, message)
def get_services(services_folder)
def _seek_forward(self, message)
def _queue(self, message)
def pulse_lower_volume(self)
def pulse_restore_volume(self)
def play(self, tracks, prefered_service, repeat=False)
def _track_info(self, message)
def setup_pulseaudio_handlers(self, pulse_choice=None)
def _prev(self, message=None)
def _list_backends(self, message)
def _pause(self, message=None)
def _resume(self, message=None)
def _lower_volume(self, message=None)
def load_services(config, bus, path=None)
def _next(self, message=None)
def _seek_backward(self, message)
def create_service_descriptor(service_folder)
def track_start(self, track)
def load_services_callback(self)
def get(phrase, lang=None, context=None)
def _stop(self, message=None)