11# AudioFlinger - Core Audio Management Service
22# Centralized audio routing with priority-based audio focus (Android-inspired)
33# Supports I2S (digital audio) and PWM buzzer (tones/ringtones)
4+ #
5+ # Simple routing: play_wav() -> I2S, play_rtttl() -> buzzer
6+ # Uses TaskManager (asyncio) for non-blocking background playback
47
5- # Device type constants
6- DEVICE_NULL = 0 # No audio hardware (desktop fallback)
7- DEVICE_I2S = 1 # Digital audio output (WAV playback)
8- DEVICE_BUZZER = 2 # PWM buzzer (tones/RTTTL)
9- DEVICE_BOTH = 3 # Both I2S and buzzer available
8+ from mpos .task_manager import TaskManager
109
1110# Stream type constants (priority order: higher number = higher priority)
1211STREAM_MUSIC = 0 # Background music (lowest priority)
1312STREAM_NOTIFICATION = 1 # Notification sounds (medium priority)
1413STREAM_ALARM = 2 # Alarms/alerts (highest priority)
1514
1615# Module-level state (singleton pattern, follows battery_voltage.py)
17- _device_type = DEVICE_NULL
1816_i2s_pins = None # I2S pin configuration dict (created per-stream)
1917_buzzer_instance = None # PWM buzzer instance
2018_current_stream = None # Currently playing stream
19+ _current_task = None # Currently running playback task
2120_volume = 50 # System volume (0-100)
22- _stream_lock = None # Thread lock for stream management
2321
2422
25- def init (device_type , i2s_pins = None , buzzer_instance = None ):
23+ def init (i2s_pins = None , buzzer_instance = None ):
2624 """
2725 Initialize AudioFlinger with hardware configuration.
2826
2927 Args:
30- device_type: One of DEVICE_NULL, DEVICE_I2S, DEVICE_BUZZER, DEVICE_BOTH
31- i2s_pins: Dict with 'sck', 'ws', 'sd' pin numbers (for I2S devices)
32- buzzer_instance: PWM instance for buzzer (for buzzer devices)
28+ i2s_pins: Dict with 'sck', 'ws', 'sd' pin numbers (for I2S/WAV playback)
29+ buzzer_instance: PWM instance for buzzer (for RTTTL playback)
3330 """
34- global _device_type , _i2s_pins , _buzzer_instance , _stream_lock
31+ global _i2s_pins , _buzzer_instance
3532
36- _device_type = device_type
3733 _i2s_pins = i2s_pins
3834 _buzzer_instance = buzzer_instance
3935
40- # Initialize thread lock for stream management
41- try :
42- import _thread
43- _stream_lock = _thread .allocate_lock ()
44- except ImportError :
45- # Desktop mode - no threading support
46- _stream_lock = None
36+ # Build status message
37+ capabilities = []
38+ if i2s_pins :
39+ capabilities .append ("I2S (WAV)" )
40+ if buzzer_instance :
41+ capabilities .append ("Buzzer (RTTTL)" )
42+
43+ if capabilities :
44+ print (f"AudioFlinger initialized: { ', ' .join (capabilities )} " )
45+ else :
46+ print ("AudioFlinger initialized: No audio hardware" )
47+
48+
49+ def has_i2s ():
50+ """Check if I2S audio is available for WAV playback."""
51+ return _i2s_pins is not None
4752
48- device_names = {
49- DEVICE_NULL : "NULL (no audio)" ,
50- DEVICE_I2S : "I2S (digital audio)" ,
51- DEVICE_BUZZER : "Buzzer (PWM tones)" ,
52- DEVICE_BOTH : "Both (I2S + Buzzer)"
53- }
5453
55- print (f"AudioFlinger initialized: { device_names .get (device_type , 'Unknown' )} " )
54+ def has_buzzer ():
55+ """Check if buzzer is available for RTTTL playback."""
56+ return _buzzer_instance is not None
5657
5758
5859def _check_audio_focus (stream_type ):
@@ -85,35 +86,27 @@ def _check_audio_focus(stream_type):
8586 return True
8687
8788
88- def _playback_thread (stream ):
89+ async def _playback_coroutine (stream ):
8990 """
90- Background thread function for audio playback.
91+ Async coroutine for audio playback.
9192
9293 Args:
9394 stream: Stream instance (WAVStream or RTTTLStream)
9495 """
95- global _current_stream
96+ global _current_stream , _current_task
9697
97- # Acquire lock and set as current stream
98- if _stream_lock :
99- _stream_lock .acquire ()
10098 _current_stream = stream
101- if _stream_lock :
102- _stream_lock .release ()
10399
104100 try :
105- # Run playback (blocks until complete or stopped)
106- stream .play ()
101+ # Run async playback
102+ await stream .play_async ()
107103 except Exception as e :
108104 print (f"AudioFlinger: Playback error: { e } " )
109105 finally :
110106 # Clear current stream
111- if _stream_lock :
112- _stream_lock .acquire ()
113107 if _current_stream == stream :
114108 _current_stream = None
115- if _stream_lock :
116- _stream_lock .release ()
109+ _current_task = None
117110
118111
119112def play_wav (file_path , stream_type = STREAM_MUSIC , volume = None , on_complete = None ):
@@ -129,29 +122,19 @@ def play_wav(file_path, stream_type=STREAM_MUSIC, volume=None, on_complete=None)
129122 Returns:
130123 bool: True if playback started, False if rejected or unavailable
131124 """
132- if _device_type not in (DEVICE_I2S , DEVICE_BOTH ):
133- print ("AudioFlinger: play_wav() failed - no I2S device available" )
134- return False
125+ global _current_task
135126
136127 if not _i2s_pins :
137- print ("AudioFlinger: play_wav() failed - I2S pins not configured" )
128+ print ("AudioFlinger: play_wav() failed - I2S not configured" )
138129 return False
139130
140131 # Check audio focus
141- if _stream_lock :
142- _stream_lock .acquire ()
143- can_start = _check_audio_focus (stream_type )
144- if _stream_lock :
145- _stream_lock .release ()
146-
147- if not can_start :
132+ if not _check_audio_focus (stream_type ):
148133 return False
149134
150- # Create stream and start playback in background thread
135+ # Create stream and start playback as async task
151136 try :
152137 from mpos .audio .stream_wav import WAVStream
153- import _thread
154- import mpos .apps
155138
156139 stream = WAVStream (
157140 file_path = file_path ,
@@ -161,8 +144,7 @@ def play_wav(file_path, stream_type=STREAM_MUSIC, volume=None, on_complete=None)
161144 on_complete = on_complete
162145 )
163146
164- _thread .stack_size (mpos .apps .good_stack_size ())
165- _thread .start_new_thread (_playback_thread , (stream ,))
147+ _current_task = TaskManager .create_task (_playback_coroutine (stream ))
166148 return True
167149
168150 except Exception as e :
@@ -183,29 +165,19 @@ def play_rtttl(rtttl_string, stream_type=STREAM_NOTIFICATION, volume=None, on_co
183165 Returns:
184166 bool: True if playback started, False if rejected or unavailable
185167 """
186- if _device_type not in (DEVICE_BUZZER , DEVICE_BOTH ):
187- print ("AudioFlinger: play_rtttl() failed - no buzzer device available" )
188- return False
168+ global _current_task
189169
190170 if not _buzzer_instance :
191- print ("AudioFlinger: play_rtttl() failed - buzzer not initialized " )
171+ print ("AudioFlinger: play_rtttl() failed - buzzer not configured " )
192172 return False
193173
194174 # Check audio focus
195- if _stream_lock :
196- _stream_lock .acquire ()
197- can_start = _check_audio_focus (stream_type )
198- if _stream_lock :
199- _stream_lock .release ()
200-
201- if not can_start :
175+ if not _check_audio_focus (stream_type ):
202176 return False
203177
204- # Create stream and start playback in background thread
178+ # Create stream and start playback as async task
205179 try :
206180 from mpos .audio .stream_rtttl import RTTTLStream
207- import _thread
208- import mpos .apps
209181
210182 stream = RTTTLStream (
211183 rtttl_string = rtttl_string ,
@@ -215,8 +187,7 @@ def play_rtttl(rtttl_string, stream_type=STREAM_NOTIFICATION, volume=None, on_co
215187 on_complete = on_complete
216188 )
217189
218- _thread .stack_size (mpos .apps .good_stack_size ())
219- _thread .start_new_thread (_playback_thread , (stream ,))
190+ _current_task = TaskManager .create_task (_playback_coroutine (stream ))
220191 return True
221192
222193 except Exception as e :
@@ -226,60 +197,38 @@ def play_rtttl(rtttl_string, stream_type=STREAM_NOTIFICATION, volume=None, on_co
226197
227198def stop ():
228199 """Stop current audio playback."""
229- global _current_stream
230-
231- if _stream_lock :
232- _stream_lock .acquire ()
200+ global _current_stream , _current_task
233201
234202 if _current_stream :
235203 _current_stream .stop ()
236204 print ("AudioFlinger: Playback stopped" )
237205 else :
238206 print ("AudioFlinger: No playback to stop" )
239207
240- if _stream_lock :
241- _stream_lock .release ()
242-
243208
244209def pause ():
245210 """
246211 Pause current audio playback (if supported by stream).
247212 Note: Most streams don't support pause, only stop.
248213 """
249- global _current_stream
250-
251- if _stream_lock :
252- _stream_lock .acquire ()
253-
254214 if _current_stream and hasattr (_current_stream , 'pause' ):
255215 _current_stream .pause ()
256216 print ("AudioFlinger: Playback paused" )
257217 else :
258218 print ("AudioFlinger: Pause not supported or no playback active" )
259219
260- if _stream_lock :
261- _stream_lock .release ()
262-
263220
264221def resume ():
265222 """
266223 Resume paused audio playback (if supported by stream).
267224 Note: Most streams don't support resume, only play.
268225 """
269- global _current_stream
270-
271- if _stream_lock :
272- _stream_lock .acquire ()
273-
274226 if _current_stream and hasattr (_current_stream , 'resume' ):
275227 _current_stream .resume ()
276228 print ("AudioFlinger: Playback resumed" )
277229 else :
278230 print ("AudioFlinger: Resume not supported or no playback active" )
279231
280- if _stream_lock :
281- _stream_lock .release ()
282-
283232
284233def set_volume (volume ):
285234 """
@@ -304,29 +253,11 @@ def get_volume():
304253 return _volume
305254
306255
307- def get_device_type ():
308- """
309- Get configured audio device type.
310-
311- Returns:
312- int: Device type (DEVICE_NULL, DEVICE_I2S, DEVICE_BUZZER, DEVICE_BOTH)
313- """
314- return _device_type
315-
316-
317256def is_playing ():
318257 """
319258 Check if audio is currently playing.
320259
321260 Returns:
322261 bool: True if playback active, False otherwise
323262 """
324- if _stream_lock :
325- _stream_lock .acquire ()
326-
327- result = _current_stream is not None and _current_stream .is_playing ()
328-
329- if _stream_lock :
330- _stream_lock .release ()
331-
332- return result
263+ return _current_stream is not None and _current_stream .is_playing ()
0 commit comments