Skip to content

Commit 78992c6

Browse files
Async feature implementation using threading library (#28)
* Add async to dev - test async feature * Test nava play * Add async and loop feature * Fix async bugs - Update documentation and test script * Async bugs: call the play function async from test script * Test script: more intuitive example of test script * Remove clear.wav - Fix documentation spacing issue * Fix period issue in documentations * Implement async feature with threading module * Async feature: Now user will get a handling thread which then could be used for waiting for the sound playing to be ended or do other tasks in the meantime. * Update documentations * Document non-blocking sound play function * Remove repetitive functions - Fix possible import issues in __init__ - Finish Linux implementation * __non_blocking_play() is removed * cleanup_processes function name changed to make sure it's imported * Linux implementation completed and tested * Implement Windows async play feature - Tested in Windows * Implement macOS async feature based on Linux * Remove test/main and test/__init__.py
1 parent 3d3ec51 commit 78992c6

File tree

4 files changed

+116
-17
lines changed

4 files changed

+116
-17
lines changed

.gitignore

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -136,4 +136,3 @@ dmypy.json
136136

137137
# Cython debug symbols
138138
cython_debug/
139-

AUTHORS.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,3 +11,4 @@
1111
# Other Contributors #
1212
----------
1313
- Zahra Mobasher ([Instagram](https://www.instagram.com/littleblackoyster/?hl=en))
14+
- Nima Samadi ([Github](https://github.com/NimaSamadi007))

nava/__init__.py

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,10 @@
22
"""Nava modules."""
33
from .params import NAVA_VERSION
44
from .errors import NavaBaseError
5-
from .functions import play
5+
from .functions import play, cleanup_processes
6+
7+
import atexit
8+
# Async play processes clean up
9+
atexit.register(cleanup_processes)
610

711
__version__ = NAVA_VERSION

nava/functions.py

Lines changed: 110 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -5,12 +5,18 @@
55
import os
66
import shlex
77
from functools import wraps
8+
import threading
89

910
from .params import OVERVIEW
1011
from .params import SOUND_FILE_PLAY_ERROR, SOUND_FILE_EXIST_ERROR
1112
from .params import SOUND_FILE_PATH_TYPE_ERROR
1213
from .errors import NavaBaseError
1314

15+
"""
16+
List of all aplay processes
17+
"""
18+
play_processes = []
19+
1420

1521
def nava_help():
1622
"""
@@ -47,42 +53,113 @@ def quoter(sound_path, *args, **kwargs):
4753
return quoter
4854

4955

50-
def __play_win(sound_path):
56+
def cleanup_processes():
57+
"""
58+
Cleanup undead play processes after module exit.
59+
60+
:return: None
61+
"""
62+
for proc in play_processes:
63+
proc.kill()
64+
proc.terminate()
65+
66+
67+
def __play_win(sound_path, is_async=True):
5168
"""
5269
Play sound in Windows.
5370
5471
:param sound_path: sound path
5572
:type sound_path: str
73+
:param is_async: play async or not
74+
:type is_async: bool
5675
:return: None
5776
"""
5877
import winsound
59-
winsound.PlaySound(sound_path, winsound.SND_FILENAME)
78+
# If is_async is ture, play async
79+
play_flags = winsound.SND_FILENAME | (is_async & winsound.SND_ASYNC)
80+
winsound.PlaySound(sound_path, play_flags)
6081

6182

6283
@quote
63-
def __play_linux(sound_path):
84+
def __play_linux(sound_path, is_async=True):
6485
"""
6586
Play sound in Linux.
6687
67-
:param sound_path: sound path
88+
:param sound_path: sound path to be played
89+
:type sound_path: str
90+
:param is_async: play async or not
91+
:type is_async: bool
92+
:return: None or sound thread (depending on async flag)
93+
"""
94+
if is_async:
95+
sound_thread = threading.Thread(target=__play_async_linux,
96+
args=(sound_path,),
97+
daemon=True)
98+
sound_thread.start()
99+
return sound_thread
100+
else:
101+
__play_sync_linux(sound_path)
102+
103+
104+
def __play_sync_linux(sound_path):
105+
"""
106+
Play sound synchronously in Linux.
107+
108+
:param sound_path: sound path to be played
68109
:type sound_path: str
69110
:return: None
70111
"""
71112
_ = subprocess.check_call(["aplay",
72-
sound_path],
73-
shell=False,
74-
stderr=subprocess.PIPE,
75-
stdin=subprocess.PIPE,
76-
stdout=subprocess.PIPE)
113+
sound_path],
114+
shell=False,
115+
stderr=subprocess.PIPE,
116+
stdin=subprocess.PIPE,
117+
stdout=subprocess.PIPE)
118+
119+
120+
def __play_async_linux(sound_path):
121+
"""
122+
Play sound asynchronously in Linux.
123+
124+
:param sound_path: sound path to be played
125+
:type sound_path: str
126+
:return: None
127+
"""
128+
proc = subprocess.Popen(["aplay",
129+
sound_path],
130+
stderr=subprocess.PIPE,
131+
stdin=subprocess.PIPE,
132+
stdout=subprocess.PIPE)
133+
play_processes.append(proc)
77134

78135

79136
@quote
80-
def __play_mac(sound_path):
137+
def __play_mac(sound_path, is_async=True):
81138
"""
82139
Play sound in macOS.
83140
84141
:param sound_path: sound path
85142
:type sound_path: str
143+
:param is_async: play sound in async mode
144+
:type is_async: bool
145+
:return: None or sound thread, depending on the flag
146+
"""
147+
if is_async:
148+
sound_thread = threading.Thread(target=__play_async_mac,
149+
args=(sound_path,),
150+
daemon=True)
151+
sound_thread.start()
152+
return sound_thread
153+
else:
154+
__play_sync_mac(sound_path)
155+
156+
157+
def __play_sync_mac(sound_path):
158+
"""
159+
Play sound synchronously in macOS.
160+
161+
:param sound_path: sound path to be played
162+
:type sound_path: str
86163
:return: None
87164
"""
88165
_ = subprocess.check_call(["afplay",
@@ -93,6 +170,22 @@ def __play_mac(sound_path):
93170
stdout=subprocess.PIPE)
94171

95172

173+
def __play_async_mac(sound_path):
174+
"""
175+
Play sound asynchronously in macOS.
176+
177+
:param sound_path: sound path to be played
178+
:type sound_path: str
179+
:return: None
180+
"""
181+
proc = subprocess.Popen(["afplay",
182+
sound_path],
183+
stderr=subprocess.PIPE,
184+
stdin=subprocess.PIPE,
185+
stdout=subprocess.PIPE)
186+
play_processes.append(proc)
187+
188+
96189
def path_check(func):
97190
"""
98191
Check the given path to be a string and a valid file directory.
@@ -122,21 +215,23 @@ def path_checker(sound_path, *args, **kwargs):
122215

123216

124217
@path_check
125-
def play(sound_path):
218+
def play(sound_path, is_async=True):
126219
"""
127220
Play sound.
128221
129222
:param sound_path: sound path
130223
:type sound_path: str
131-
:return: None
224+
:param is_async: play synchronously or asynchronously (async by default)
225+
:type is_async: bool
226+
:return: None or sound thread for futher handlings
132227
"""
133228
try:
134229
sys_platform = sys.platform
135230
if sys_platform == "win32":
136-
__play_win(sound_path)
231+
__play_win(sound_path, is_async)
137232
elif sys_platform == "darwin":
138-
__play_mac(sound_path)
233+
return __play_mac(sound_path, is_async)
139234
else:
140-
__play_linux(sound_path)
235+
return __play_linux(sound_path, is_async)
141236
except Exception: # pragma: no cover
142237
raise NavaBaseError(SOUND_FILE_PLAY_ERROR)

0 commit comments

Comments
 (0)