Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Async mode fix #29

Merged
merged 32 commits into from
Jan 18, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
32 commits
Select commit Hold shift + click to select a range
ac161fe
feat : NavaThread added
sepandhaghighi Jan 16, 2024
e10c307
fix : linux and mac async functions removed
sepandhaghighi Jan 16, 2024
d8fe2a2
fix : __play_win function updated
sepandhaghighi Jan 16, 2024
4d5e264
feat : sound_id_gen function added
sepandhaghighi Jan 16, 2024
25892c1
feat : stop and stop_all functions added
sepandhaghighi Jan 16, 2024
2e83244
fix : use NavaThread instead of Thread
sepandhaghighi Jan 16, 2024
bfc4d30
fix : minor bug in play function windows section fixed
sepandhaghighi Jan 16, 2024
3954a8b
fix : cleanup_processes function removed
sepandhaghighi Jan 16, 2024
7e1987f
fix : minor bug in subprocess call fixed
sepandhaghighi Jan 16, 2024
be13c61
fix : minor edit in tests
sepandhaghighi Jan 16, 2024
106cd08
fix : try to fix windows test bug
sepandhaghighi Jan 16, 2024
6549a33
fix : try to fix windows test bug
sepandhaghighi Jan 16, 2024
8c5fa3a
doc : NavaThread class docstring updated
sepandhaghighi Jan 16, 2024
5dd7a59
doc : functions docstrings updated
sepandhaghighi Jan 16, 2024
ab1a699
doc : functions docstrings updated
sepandhaghighi Jan 16, 2024
c0b973e
fix : __play_win_by_flags functions renamed to __play_win_flags
sepandhaghighi Jan 16, 2024
3ad0ff2
fix : is_async renamed to async_mode
sepandhaghighi Jan 16, 2024
0955d07
doc : async_mode docstring updated
sepandhaghighi Jan 16, 2024
f42740e
fix : autopep8 scripts updated
sepandhaghighi Jan 16, 2024
eff7e52
fix : autopep8
sepandhaghighi Jan 16, 2024
b9f52df
doc : NavaThread docstring bug fixed
sepandhaghighi Jan 16, 2024
024e7c5
fix : stop function error added
sepandhaghighi Jan 16, 2024
d63fa9a
fix : error_test updated
sepandhaghighi Jan 16, 2024
8175182
fix : function_test updated
sepandhaghighi Jan 16, 2024
9face2d
doc : CHANGELOG.md updated
sepandhaghighi Jan 16, 2024
297b9f1
doc : README.md updated
sepandhaghighi Jan 16, 2024
b73352a
fix : async_mode default value set to False
sepandhaghighi Jan 16, 2024
782fadd
fix : minor edit in async_mode test
sepandhaghighi Jan 16, 2024
9355cdd
doc : README.md async section updated
sepandhaghighi Jan 16, 2024
b9abdd7
fix : minor bug in linux/mac sync mode fixed
sepandhaghighi Jan 17, 2024
b0f52fc
fix : minor edit in __paly_win function
sepandhaghighi Jan 18, 2024
435e701
doc : CHANGELOG updated
sepandhaghighi Jan 18, 2024
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 3 additions & 1 deletion .github/workflows/windows_test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -33,9 +33,11 @@ jobs:
- name: Install Scream
shell: powershell
run: |
Set-PSRepository -Name 'PSGallery' -InstallationPolicy Trusted
Install-Module -Name 7Zip4PowerShell -Force
Start-Service audio*
Invoke-WebRequest https://github.com/duncanthrax/scream/releases/download/3.6/Scream3.6.zip -OutFile C:\Scream3.6.zip
Extract-7Zip -Path C:\Scream3.6.zip -DestinationPath C:\Scream
Expand-7Zip -ArchiveFileName C:\Scream3.6.zip -TargetPath C:\Scream
$cert = (Get-AuthenticodeSignature C:\Scream\Install\driver\Scream.sys).SignerCertificate
$store = [System.Security.Cryptography.X509Certificates.X509Store]::new("TrustedPublisher", "LocalMachine")
$store.Open("ReadWrite")
Expand Down
6 changes: 6 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,12 @@ The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/)
and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.html).

## [Unreleased]
### Added
- `NavaThread` class
- `stop` function
- `stop_all` function
### Changed
- `async_mode` parameter added to `play` function
## [0.2] - 2023-07-10
### Added
- Logo
Expand Down
13 changes: 13 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -98,11 +98,24 @@ Nava is a Python library that allows users to play sound in Python without any d
## Usage

### Basic

```python
from nava import play
play("alarm.wav")
```

### Async mode

⚠️ The `async_mode` parameter has a default value of `False`

```python
import time
from nava import play, stop
sound_id = play("alarm.wav", async_mode=True)
time.sleep(4)
stop(sound_id)
```

### Error

```python
Expand Down
6 changes: 3 additions & 3 deletions autopep8.bat
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
python -m autopep8 nava --recursive --aggressive --aggressive --in-place --pep8-passes 2000 --verbose
python -m autopep8 setup.py --recursive --aggressive --aggressive --in-place --pep8-passes 2000 --verbose
python -m autopep8 others --recursive --aggressive --aggressive --in-place --pep8-passes 2000 --verbose
python -m autopep8 nava --recursive --aggressive --aggressive --in-place --pep8-passes 2000 --max-line-length 120 --verbose
python -m autopep8 setup.py --recursive --aggressive --aggressive --in-place --pep8-passes 2000 --max-line-length 120 --verbose
python -m autopep8 others --recursive --aggressive --aggressive --in-place --pep8-passes 2000 --max-line-length 120 --verbose
6 changes: 3 additions & 3 deletions autopep8.sh
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
#!/bin/bash
python -m autopep8 nava --recursive --aggressive --aggressive --in-place --pep8-passes 2000 --verbose
python -m autopep8 setup.py --recursive --aggressive --aggressive --in-place --pep8-passes 2000 --verbose
python -m autopep8 others --recursive --aggressive --aggressive --in-place --pep8-passes 2000 --verbose
python -m autopep8 nava --recursive --aggressive --aggressive --in-place --pep8-passes 2000 --max-line-length 120 --verbose
python -m autopep8 setup.py --recursive --aggressive --aggressive --in-place --pep8-passes 2000 --max-line-length 120 --verbose
python -m autopep8 others --recursive --aggressive --aggressive --in-place --pep8-passes 2000 --max-line-length 120 --verbose
7 changes: 3 additions & 4 deletions nava/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,9 @@
"""Nava modules."""
from .params import NAVA_VERSION
from .errors import NavaBaseError
from .functions import play, cleanup_processes

from .functions import play, stop, stop_all
import atexit
# Async play processes clean up
atexit.register(cleanup_processes)

atexit.register(stop_all)

__version__ = NAVA_VERSION
200 changes: 108 additions & 92 deletions nava/functions.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,17 +5,46 @@
import os
import shlex
from functools import wraps
import threading

from .thread import NavaThread
from .params import OVERVIEW
from .params import SOUND_FILE_PLAY_ERROR, SOUND_FILE_EXIST_ERROR
from .params import SOUND_FILE_PATH_TYPE_ERROR
from .params import SOUND_FILE_PATH_TYPE_ERROR, SOUND_ID_EXIST_ERROR
from .errors import NavaBaseError
from . import params

"""
List of all aplay processes
"""
play_processes = []

def stop(sound_id):
"""
Stop sound.

:param sound_id: sound id
:type sound_id: int
:return: None
"""
if sound_id not in params._play_threads_map:
raise NavaBaseError(SOUND_ID_EXIST_ERROR)
params._play_threads_map[sound_id].stop()


def stop_all():
"""
Stop all sounds.

:return: None
"""
for thread in params._play_threads_map.values():
thread.stop()


def sound_id_gen():
"""
Sound id generator.

:return: sound id as int
"""
params._play_threads_counter += 1
sound_id = params._play_threads_counter + 1000
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why did you add 1000 offset. If it's necessary, what do you think of transferring it to params.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For better readability, we may change the format of the sound_id to something like Nava-1, Nava-2, etc. in the future.

return sound_id


def nava_help():
Expand Down Expand Up @@ -53,137 +82,124 @@
return quoter


def cleanup_processes():
def __play_win(sound_path, async_mode=False):
"""
Cleanup undead play processes after module exit.
Play sound in Windows.

:return: None
:param sound_path: sound path
:type sound_path: str
:param async_mode: async mode flag
:type async_mode: bool
:return: None or sound id
"""
for proc in play_processes:
proc.kill()
proc.terminate()
import winsound
play_flags = winsound.SND_FILENAME | (async_mode & winsound.SND_ASYNC)

Check warning on line 96 in nava/functions.py

View check run for this annotation

Codecov / codecov/patch

nava/functions.py#L95-L96

Added lines #L95 - L96 were not covered by tests

if async_mode:
sound_thread = NavaThread(target=__play_win_flags,

Check warning on line 99 in nava/functions.py

View check run for this annotation

Codecov / codecov/patch

nava/functions.py#L99

Added line #L99 was not covered by tests
args=(sound_path, play_flags), daemon=True)
sound_thread.start()
sound_id = sound_id_gen()
params._play_threads_map[sound_id] = sound_thread
return sound_id

Check warning on line 104 in nava/functions.py

View check run for this annotation

Codecov / codecov/patch

nava/functions.py#L101-L104

Added lines #L101 - L104 were not covered by tests
else:
winsound.PlaySound(sound_path, play_flags)

Check warning on line 106 in nava/functions.py

View check run for this annotation

Codecov / codecov/patch

nava/functions.py#L106

Added line #L106 was not covered by tests


def __play_win(sound_path, is_async=True):
def __play_win_flags(sound_path, flags):
"""
Play sound in Windows.
Play sound in Windows using different flags.

:param sound_path: sound path
:type sound_path: str
:param is_async: play async or not
:type is_async: bool
:param flags: different mode flags
:type flags: winsound flags
:return: None
"""
import winsound
# If is_async is ture, play async
play_flags = winsound.SND_FILENAME | (is_async & winsound.SND_ASYNC)
winsound.PlaySound(sound_path, play_flags)
winsound.PlaySound(sound_path, flags)

Check warning on line 120 in nava/functions.py

View check run for this annotation

Codecov / codecov/patch

nava/functions.py#L120

Added line #L120 was not covered by tests


@quote
def __play_linux(sound_path, is_async=True):
def __play_linux(sound_path, async_mode=False):
"""
Play sound in Linux.

: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)
:param async_mode: async mode flag
:type async_mode: bool
:return: None or sound id
"""
if async_mode:
sound_thread = NavaThread(target=__play_proc_linux,
args=(sound_path,),
daemon=True)
sound_thread.start()
return sound_thread
sound_id = sound_id_gen()
params._play_threads_map[sound_id] = sound_thread
return sound_id
else:
__play_sync_linux(sound_path)


def __play_sync_linux(sound_path):
"""
Play sound synchronously in Linux.
proc = __play_proc_linux(sound_path)
proc.wait()

: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)


def __play_async_linux(sound_path):
def __play_proc_linux(sound_path):
"""
Play sound asynchronously in Linux.
Create sound playing process in Linux.

:param sound_path: sound path to be played
:type sound_path: str
:return: None
:return: process
"""
proc = subprocess.Popen(["aplay",
sound_path],
sound_path],
shell=False,
stderr=subprocess.PIPE,
stdin=subprocess.PIPE,
stdout=subprocess.PIPE)
play_processes.append(proc)
return proc


@quote
def __play_mac(sound_path, is_async=True):
def __play_mac(sound_path, async_mode=False):
"""
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)
:param async_mode: async mode flag
:type async_mode: bool
:return: None or sound id
"""
if async_mode:
sound_thread = NavaThread(target=__play_proc_mac,

Check warning on line 176 in nava/functions.py

View check run for this annotation

Codecov / codecov/patch

nava/functions.py#L176

Added line #L176 was not covered by tests
args=(sound_path,),
daemon=True)
sound_thread.start()
return sound_thread
sound_id = sound_id_gen()
params._play_threads_map[sound_id] = sound_thread
return sound_id

Check warning on line 182 in nava/functions.py

View check run for this annotation

Codecov / codecov/patch

nava/functions.py#L180-L182

Added lines #L180 - L182 were not covered by tests
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",
sound_path],
shell=False,
stderr=subprocess.PIPE,
stdin=subprocess.PIPE,
stdout=subprocess.PIPE)
proc = __play_proc_mac(sound_path)
proc.wait()

Check warning on line 185 in nava/functions.py

View check run for this annotation

Codecov / codecov/patch

nava/functions.py#L184-L185

Added lines #L184 - L185 were not covered by tests


def __play_async_mac(sound_path):
def __play_proc_mac(sound_path):
"""
Play sound asynchronously in macOS.
Create sound playing process in macOS.

:param sound_path: sound path to be played
:type sound_path: str
:return: None
:return: process
"""
proc = subprocess.Popen(["afplay",
sound_path],
sound_path],
shell=False,
stderr=subprocess.PIPE,
stdin=subprocess.PIPE,
stdout=subprocess.PIPE)
play_processes.append(proc)
return proc

Check warning on line 202 in nava/functions.py

View check run for this annotation

Codecov / codecov/patch

nava/functions.py#L202

Added line #L202 was not covered by tests


def path_check(func):
Expand Down Expand Up @@ -215,23 +231,23 @@


@path_check
def play(sound_path, is_async=True):
def play(sound_path, async_mode=False):
"""
Play sound.

:param sound_path: sound path
:type sound_path: str
:param is_async: play synchronously or asynchronously (async by default)
:type is_async: bool
:return: None or sound thread for futher handlings
:param async_mode: async mode flag
:type async_mode: bool
:return: None or sound id
"""
try:
sys_platform = sys.platform
if sys_platform == "win32":
__play_win(sound_path, is_async)
return __play_win(sound_path, async_mode)

Check warning on line 247 in nava/functions.py

View check run for this annotation

Codecov / codecov/patch

nava/functions.py#L247

Added line #L247 was not covered by tests
elif sys_platform == "darwin":
return __play_mac(sound_path, is_async)
return __play_mac(sound_path, async_mode)

Check warning on line 249 in nava/functions.py

View check run for this annotation

Codecov / codecov/patch

nava/functions.py#L249

Added line #L249 was not covered by tests
else:
return __play_linux(sound_path, is_async)
except Exception: # pragma: no cover
return __play_linux(sound_path, async_mode)
except Exception: # pragma: no cover
raise NavaBaseError(SOUND_FILE_PLAY_ERROR)
4 changes: 4 additions & 0 deletions nava/params.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,3 +11,7 @@
SOUND_FILE_PLAY_ERROR = "Sound can not play due to some issues."
SOUND_FILE_EXIST_ERROR = "Given sound file doesn't exist."
SOUND_FILE_PATH_TYPE_ERROR = "Sound file's path should be a string."
SOUND_ID_EXIST_ERROR = "Given sound id doesn't exist."

_play_threads_map = dict()
_play_threads_counter = 0
Loading
Loading