diff --git a/.gitignore b/.gitignore index 73358ad..a81c8ee 100644 --- a/.gitignore +++ b/.gitignore @@ -136,4 +136,3 @@ dmypy.json # Cython debug symbols cython_debug/ - diff --git a/AUTHORS.md b/AUTHORS.md index 27f620d..e1540c2 100644 --- a/AUTHORS.md +++ b/AUTHORS.md @@ -11,3 +11,4 @@ # Other Contributors # ---------- - Zahra Mobasher ([Instagram](https://www.instagram.com/littleblackoyster/?hl=en)) +- Nima Samadi ([Github](https://github.com/NimaSamadi007)) \ No newline at end of file diff --git a/nava/__init__.py b/nava/__init__.py index 3f535d0..6ed050a 100644 --- a/nava/__init__.py +++ b/nava/__init__.py @@ -2,6 +2,10 @@ """Nava modules.""" from .params import NAVA_VERSION from .errors import NavaBaseError -from .functions import play +from .functions import play, cleanup_processes + +import atexit +# Async play processes clean up +atexit.register(cleanup_processes) __version__ = NAVA_VERSION diff --git a/nava/functions.py b/nava/functions.py index f7c531b..cb2953a 100644 --- a/nava/functions.py +++ b/nava/functions.py @@ -5,12 +5,18 @@ import os import shlex from functools import wraps +import threading from .params import OVERVIEW from .params import SOUND_FILE_PLAY_ERROR, SOUND_FILE_EXIST_ERROR from .params import SOUND_FILE_PATH_TYPE_ERROR from .errors import NavaBaseError +""" +List of all aplay processes +""" +play_processes = [] + def nava_help(): """ @@ -47,42 +53,113 @@ def quoter(sound_path, *args, **kwargs): return quoter -def __play_win(sound_path): +def cleanup_processes(): + """ + Cleanup undead play processes after module exit. + + :return: None + """ + for proc in play_processes: + proc.kill() + proc.terminate() + + +def __play_win(sound_path, is_async=True): """ Play sound in Windows. :param sound_path: sound path :type sound_path: str + :param is_async: play async or not + :type is_async: bool :return: None """ import winsound - winsound.PlaySound(sound_path, winsound.SND_FILENAME) + # If is_async is ture, play async + play_flags = winsound.SND_FILENAME | (is_async & winsound.SND_ASYNC) + winsound.PlaySound(sound_path, play_flags) @quote -def __play_linux(sound_path): +def __play_linux(sound_path, is_async=True): """ Play sound in Linux. - :param sound_path: sound path + :param sound_path: sound path to be played + :type sound_path: str + :param is_async: play async or not + :type is_async: bool + :return: None or sound thread (depending on async flag) + """ + if is_async: + sound_thread = threading.Thread(target=__play_async_linux, + args=(sound_path,), + daemon=True) + sound_thread.start() + return sound_thread + else: + __play_sync_linux(sound_path) + + +def __play_sync_linux(sound_path): + """ + Play sound synchronously in Linux. + + :param sound_path: sound path to be played :type sound_path: str :return: None """ _ = subprocess.check_call(["aplay", - sound_path], - shell=False, - stderr=subprocess.PIPE, - stdin=subprocess.PIPE, - stdout=subprocess.PIPE) + sound_path], + shell=False, + stderr=subprocess.PIPE, + stdin=subprocess.PIPE, + stdout=subprocess.PIPE) + + +def __play_async_linux(sound_path): + """ + Play sound asynchronously in Linux. + + :param sound_path: sound path to be played + :type sound_path: str + :return: None + """ + proc = subprocess.Popen(["aplay", + sound_path], + stderr=subprocess.PIPE, + stdin=subprocess.PIPE, + stdout=subprocess.PIPE) + play_processes.append(proc) @quote -def __play_mac(sound_path): +def __play_mac(sound_path, is_async=True): """ Play sound in macOS. :param sound_path: sound path :type sound_path: str + :param is_async: play sound in async mode + :type is_async: bool + :return: None or sound thread, depending on the flag + """ + if is_async: + sound_thread = threading.Thread(target=__play_async_mac, + args=(sound_path,), + daemon=True) + sound_thread.start() + return sound_thread + else: + __play_sync_mac(sound_path) + + +def __play_sync_mac(sound_path): + """ + Play sound synchronously in macOS. + + :param sound_path: sound path to be played + :type sound_path: str :return: None """ _ = subprocess.check_call(["afplay", @@ -93,6 +170,22 @@ def __play_mac(sound_path): stdout=subprocess.PIPE) +def __play_async_mac(sound_path): + """ + Play sound asynchronously in macOS. + + :param sound_path: sound path to be played + :type sound_path: str + :return: None + """ + proc = subprocess.Popen(["afplay", + sound_path], + stderr=subprocess.PIPE, + stdin=subprocess.PIPE, + stdout=subprocess.PIPE) + play_processes.append(proc) + + def path_check(func): """ Check the given path to be a string and a valid file directory. @@ -122,21 +215,23 @@ def path_checker(sound_path, *args, **kwargs): @path_check -def play(sound_path): +def play(sound_path, is_async=True): """ Play sound. :param sound_path: sound path :type sound_path: str - :return: None + :param is_async: play synchronously or asynchronously (async by default) + :type is_async: bool + :return: None or sound thread for futher handlings """ try: sys_platform = sys.platform if sys_platform == "win32": - __play_win(sound_path) + __play_win(sound_path, is_async) elif sys_platform == "darwin": - __play_mac(sound_path) + return __play_mac(sound_path, is_async) else: - __play_linux(sound_path) + return __play_linux(sound_path, is_async) except Exception: # pragma: no cover raise NavaBaseError(SOUND_FILE_PLAY_ERROR)