scripts/mycroft/audio/services/simple/__init__.py
Go to the documentation of this file.
1 # Copyright 2017 Mycroft AI Inc.
2 #
3 # Licensed under the Apache License, Version 2.0 (the "License");
4 # you may not use this file except in compliance with the License.
5 # You may obtain a copy of the License at
6 #
7 # http://www.apache.org/licenses/LICENSE-2.0
8 #
9 # Unless required by applicable law or agreed to in writing, software
10 # distributed under the License is distributed on an "AS IS" BASIS,
11 # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 # See the License for the specific language governing permissions and
13 # limitations under the License.
14 #
15 import signal
16 from time import sleep
17 
18 from mycroft.audio.services import AudioBackend
19 from mycroft.messagebus.message import Message
20 from mycroft.util.log import LOG
21 from mycroft.util import play_mp3, play_ogg, play_wav
22 import mimetypes
23 from requests import Session
24 
25 
26 def find_mime(path):
27  mime = None
28  if path.startswith('http'):
29  response = Session().head(path, allow_redirects=True)
30  if 200 <= response.status_code < 300:
31  mime = response.headers['content-type']
32  if not mime:
33  mime = mimetypes.guess_type(path)[0]
34 
35  if mime:
36  return mime.split('/')
37  else:
38  return (None, None)
39 
40 
42  """
43  Simple Audio backend for both mpg123 and the ogg123 player.
44  This one is rather limited and only implements basic usage.
45  """
46 
47  def __init__(self, config, bus, name='simple'):
48  super().__init__(config, bus)
49  self.config = config
50  self.process = None
51  self.bus = bus
52  self.name = name
53  self._stop_signal = False
54  self._is_playing = False
55  self._paused = False
56  self.tracks = []
57  self.index = 0
58  self.supports_mime_hints = True
59  mimetypes.init()
60 
61  self.bus.on('SimpleAudioServicePlay', self._play)
62 
63  def supported_uris(self):
64  return ['file', 'http']
65 
66  def clear_list(self):
67  self.tracks = []
68 
69  def add_list(self, tracks):
70  self.tracks += tracks
71  LOG.info("Track list is " + str(tracks))
72 
73  def _play(self, message):
74  """ Implementation specific async method to handle playback.
75  This allows mpg123 service to use the "next method as well
76  as basic play/stop.
77  """
78  LOG.info('SimpleAudioService._play')
79 
80  # Stop any existing audio playback
82 
83  repeat = message.data.get('repeat', False)
84  self._is_playing = True
85  self._paused = False
86  if isinstance(self.tracks[self.index], list):
87  track = self.tracks[self.index][0]
88  mime = self.tracks[self.index][1]
89  mime = mime.split('/')
90  else: # Assume string
91  track = self.tracks[self.index]
92  mime = find_mime(track)
93  # Indicate to audio service which track is being played
94  if self._track_start_callback:
95  self._track_start_callback(track)
96 
97  # Replace file:// uri's with normal paths
98  track = track.replace('file://', '')
99  try:
100  if 'mpeg' in mime[1]:
101  self.process = play_mp3(track)
102  elif 'ogg' in mime[1]:
103  self.process = play_ogg(track)
104  elif 'wav' in mime[1]:
105  self.process = play_wav(track)
106  else:
107  # If no mime info could be determined guess mp3
108  self.process = play_mp3(track)
109  except FileNotFoundError as e:
110  LOG.error('Couldn\'t play audio, {}'.format(repr(e)))
111  self.process = None
112 
113  # Wait for completion or stop request
114  while (self._is_process_running() and not self._stop_signal):
115  sleep(0.25)
116 
117  if self._stop_signal:
118  self._stop_running_process()
119  self._is_playing = False
120  self._paused = False
121  return
122  else:
123  self.process = None
124 
125  # if there are more tracks available play next
126  self.index += 1
127  if self.index < len(self.tracks) or repeat:
128  if self.index >= len(self.tracks):
129  self.index = 0
130  self.bus.emit(Message('SimpleAudioServicePlay',
131  {'repeat': repeat}))
132  else:
133  self._is_playing = False
134  self._paused = False
135 
136  def play(self, repeat=False):
137  LOG.info('Call SimpleAudioServicePlay')
138  self.index = 0
139  self.bus.emit(Message('SimpleAudioServicePlay', {'repeat': repeat}))
140 
141  def stop(self):
142  LOG.info('SimpleAudioServiceStop')
143  self._stop_signal = True
144  while self._is_playing:
145  sleep(0.1)
146  self._stop_signal = False
147 
148  def _pause(self):
149  """ Pauses playback if possible.
150 
151  Returns: (bool) New paused status:
152  """
153  if self.process:
154  # Suspend the playback process
155  self.process.send_signal(signal.SIGSTOP)
156  return True # After pause the service is paused
157  else:
158  return False
159 
160  def pause(self):
161  if not self._paused:
162  self._paused = self._pause()
163 
164  def _resume(self):
165  """ Resumes playback if possible.
166 
167  Returns: (bool) New paused status:
168  """
169  if self.process:
170  # Resume the playback process
171  self.process.send_signal(signal.SIGCONT)
172  return False # After resume the service is no longer paused
173  else:
174  return True
175 
176  def resume(self):
177  if self._paused:
178  # Resume the playback process
179  self._paused = self._resume()
180 
181  def next(self):
182  # Terminate process to continue to next
183  self._stop_running_process()
184 
185  def previous(self):
186  pass
187 
188  def lower_volume(self):
189  if not self._paused:
190  self._pause() # poor-man's ducking
191 
192  def restore_volume(self):
193  if not self._paused:
194  self._resume() # poor-man's unducking
195 
197  return self.process and self.process.poll() is None
198 
200  if self._is_process_running():
201  if self._paused:
202  # The child process must be "unpaused" in order to be stopped
203  self._resume()
204  self.process.terminate()
205  countdown = 10
206  while self._is_process_running() and countdown > 0:
207  sleep(0.1)
208  countdown -= 1
209 
210  if self._is_process_running():
211  # Failed to shutdown when asked nicely. Force the issue.
212  LOG.debug("Killing currently playing audio...")
213  self.process.kill()
214  self.process = None
215 
216 
217 def load_service(base_config, bus):
218  backends = base_config.get('backends', [])
219  services = [(b, backends[b]) for b in backends
220  if backends[b]['type'] == 'simple' and
221  backends[b].get('active', True)]
222  instances = [SimpleAudioService(s[1], bus, s[0]) for s in services]
223  return instances
def get(phrase, lang=None, context=None)


mycroft_ros
Author(s):
autogenerated on Mon Apr 26 2021 02:35:40