playsound3 3.2.2__tar.gz → 3.2.4__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: playsound3
3
- Version: 3.2.2
3
+ Version: 3.2.4
4
4
  Summary: Cross-platform library to play audio files
5
5
  Project-URL: Home, https://github.com/sjmikler/playsound3
6
6
  Project-URL: Issues, https://github.com/sjmikler/playsound3/issues
@@ -29,6 +29,10 @@ Classifier: Typing :: Typed
29
29
  Requires-Python: >=3.7
30
30
  Requires-Dist: pywin32; sys_platform == 'win32'
31
31
  Requires-Dist: typing-extensions; python_version < '3.8'
32
+ Provides-Extra: dev
33
+ Requires-Dist: pyright; extra == 'dev'
34
+ Requires-Dist: pytest; extra == 'dev'
35
+ Requires-Dist: ruff; extra == 'dev'
32
36
  Description-Content-Type: text/markdown
33
37
 
34
38
  > **Version 3.0.0**
@@ -1,5 +1,5 @@
1
1
  __license__ = "MIT"
2
- __version__ = "3.2.2"
2
+ __version__ = "3.2.4"
3
3
  __author__ = "Szymon Mikler"
4
4
 
5
5
  from playsound3.playsound3 import (
@@ -141,7 +141,10 @@ class Ffplay(SoundBackend):
141
141
  return False
142
142
 
143
143
  def play(self, sound: str) -> subprocess.Popen[bytes]:
144
- return subprocess.Popen(["ffplay", "-nodisp", "-autoexit", "-loglevel", "quiet", sound])
144
+ return subprocess.Popen(
145
+ ["ffplay", "-nodisp", "-autoexit", "-loglevel", "quiet", sound],
146
+ stdout=subprocess.DEVNULL,
147
+ )
145
148
 
146
149
 
147
150
  class Wmplayer(SoundBackend):
@@ -342,16 +345,16 @@ AVAILABLE_BACKENDS: list[str] = [name for name in _BACKEND_PREFERENCE if _BACKEN
342
345
  DEFAULT_BACKEND: str | None = _auto_select_backend()
343
346
 
344
347
 
345
- # This funciton is defined here because:
348
+ # This function is defined here at the bottom because of:
346
349
  # SyntaxError: annotated name 'DEFAULT_BACKEND' can't be global
347
350
  def prefer_backends(*backends: str) -> str | None:
348
351
  """Add backends to the top of the preference list.
349
352
 
350
- This function sets a soft-preference.
351
- Backend selected here are used only if available on the system.
352
- Function can be used to update the preference for a specific system
353
- without breaking the cross-platform functionality.
354
- After updating the preferences, a new default backend will be selected.
353
+ This function overrides the default backend preference.
354
+ Backend selected here will be used ONLY if available on the system.
355
+ This means this function can be used to update the preference for a
356
+ specific platform without breaking the cross-platform functionality.
357
+ After updating the preferences, the new default backend is returned.
355
358
 
356
359
  Args:
357
360
  backends: Names of the backends to prefer.
@@ -45,11 +45,18 @@ Home = "https://github.com/sjmikler/playsound3"
45
45
  Issues = "https://github.com/sjmikler/playsound3/issues"
46
46
  Documentation = "https://github.com/sjmikler/playsound3/blob/main/README.md#quick-start"
47
47
 
48
+ [project.optional-dependencies]
49
+ dev = [
50
+ "pyright",
51
+ "pytest",
52
+ "ruff",
53
+ ]
54
+
48
55
  [tool.hatch.version]
49
56
  path = "playsound3/__init__.py"
50
57
 
51
58
  [tool.hatch.build.targets.sdist]
52
- include = ["playsound3", "README.md"]
59
+ include = ["playsound3", "README.md", "tests"]
53
60
 
54
61
  [tool.hatch.build.targets.wheel]
55
62
  packages = ["playsound3"]
@@ -0,0 +1,143 @@
1
+ import os
2
+ import time
3
+
4
+ from playsound3 import AVAILABLE_BACKENDS, playsound
5
+ from playsound3.playsound3 import _prepare_path
6
+
7
+ loc_mp3_3s = "tests/sounds/sample3s.mp3"
8
+ loc_flc_3s = "tests/sounds/sample3s.flac"
9
+ web_wav_3s = "https://samplelib.com/lib/preview/wav/sample-3s.wav"
10
+
11
+ # Download web files to the local cache
12
+ for url in [web_wav_3s]:
13
+ _prepare_path(url)
14
+
15
+
16
+ def get_supported_sounds(backend):
17
+ not_supporting_flac = ["alsa", "winmm"]
18
+
19
+ if backend in not_supporting_flac:
20
+ return [loc_mp3_3s, web_wav_3s]
21
+ else:
22
+ return [loc_mp3_3s, loc_flc_3s, web_wav_3s]
23
+
24
+
25
+ CI = os.environ.get("CI", False)
26
+
27
+
28
+ def test_blocking_1():
29
+ for backend in AVAILABLE_BACKENDS:
30
+ for path in get_supported_sounds(backend):
31
+ t0 = time.perf_counter()
32
+ sound = playsound(path, block=True, backend=backend)
33
+
34
+ td = time.perf_counter() - t0
35
+ assert not sound.is_alive(), f"backend={backend}, path={path}"
36
+ assert td >= 3.0, f"backend={backend}, path={path}"
37
+ assert CI or td < 5.0, f"backend={backend}, path={path}"
38
+
39
+
40
+ def test_waiting_1():
41
+ for backend in AVAILABLE_BACKENDS:
42
+ for path in get_supported_sounds(backend):
43
+ t0 = time.perf_counter()
44
+ sound = playsound(path, block=False, backend=backend)
45
+ assert sound.is_alive(), f"backend={backend}, path={path}"
46
+
47
+ sound.wait()
48
+ td = time.perf_counter() - t0
49
+ assert not sound.is_alive(), f"backend={backend}, path={path}"
50
+ assert td >= 3.0, f"backend={backend}, path={path}"
51
+ assert CI or td < 5.0, f"backend={backend}, path={path}"
52
+
53
+
54
+ def test_waiting_2():
55
+ for backend in AVAILABLE_BACKENDS:
56
+ for path in get_supported_sounds(backend):
57
+ sound = playsound(path, block=False, backend=backend)
58
+ assert sound.is_alive(), f"backend={backend}, path={path}"
59
+
60
+ time.sleep(5)
61
+ assert not sound.is_alive(), f"backend={backend}, path={path}"
62
+
63
+
64
+ def test_stopping_1():
65
+ for backend in AVAILABLE_BACKENDS:
66
+ for path in get_supported_sounds(backend):
67
+ t0 = time.perf_counter()
68
+ sound = playsound(path, block=False, backend=backend)
69
+ assert sound.is_alive(), f"backend={backend}, path={path}"
70
+
71
+ time.sleep(1)
72
+ sound.stop()
73
+ td = time.perf_counter() - t0
74
+
75
+ time.sleep(0.05)
76
+ assert not sound.is_alive(), f"backend={backend}, path={path}"
77
+ assert td >= 0.95 and td < 2.0, f"backend={backend}, path={path}"
78
+
79
+ # Stopping again should be a no-op
80
+ sound.stop()
81
+ assert not sound.is_alive(), f"backend={backend}, path={path}"
82
+
83
+
84
+ def test_parallel_1():
85
+ for backend in AVAILABLE_BACKENDS:
86
+ for path in get_supported_sounds(backend):
87
+ t0 = time.perf_counter()
88
+ sounds = [playsound(path, block=False, backend=backend) for _ in range(3)]
89
+ time.sleep(0.05)
90
+ for sound in sounds:
91
+ assert sound.is_alive(), f"backend={backend}"
92
+ time.sleep(1)
93
+
94
+ sounds[1].stop()
95
+ time.sleep(0.05)
96
+ assert sounds[0].is_alive(), f"backend={backend}"
97
+ assert sounds[2].is_alive(), f"backend={backend}"
98
+ assert not sounds[1].is_alive(), f"backend={backend}"
99
+ time.sleep(1)
100
+
101
+ assert sounds[0].is_alive(), f"backend={backend}"
102
+ assert sounds[2].is_alive(), f"backend={backend}"
103
+ sounds[0].stop()
104
+ sounds[2].stop()
105
+ td = time.perf_counter() - t0
106
+
107
+ time.sleep(0.05)
108
+ for sound in sounds:
109
+ assert not sound.is_alive(), f"backend={backend}"
110
+ assert td >= 2.0 and td < 3.0, f"backend={backend}"
111
+
112
+
113
+ def test_parallel_2():
114
+ N_PARALLEL = 10 # Careful - this might be loud!
115
+
116
+ for backend in AVAILABLE_BACKENDS:
117
+ for path in get_supported_sounds(backend):
118
+ sounds = [playsound(path, block=False, backend=backend) for _ in range(N_PARALLEL)]
119
+
120
+ time.sleep(1)
121
+ for sound in sounds:
122
+ assert sound.is_alive(), f"backend={backend}, path={path}"
123
+ for sound in sounds:
124
+ sound.stop()
125
+
126
+ time.sleep(0.05)
127
+ for sound in sounds:
128
+ assert not sound.is_alive(), f"backend={backend}, path={path}"
129
+
130
+
131
+ def test_parallel_3():
132
+ for backend in AVAILABLE_BACKENDS:
133
+ sounds = [playsound(path, block=False, backend=backend) for path in get_supported_sounds(backend)]
134
+
135
+ time.sleep(1)
136
+ for sound in sounds:
137
+ assert sound.is_alive(), f"backend={backend}"
138
+ for sound in sounds:
139
+ sound.stop()
140
+
141
+ time.sleep(0.05)
142
+ for sound in sounds:
143
+ assert not sound.is_alive(), f"backend={backend}"
@@ -0,0 +1,29 @@
1
+ import urllib.error
2
+
3
+ import pytest
4
+
5
+ from playsound3 import playsound
6
+ from playsound3.playsound3 import PlaysoundException
7
+
8
+ valid = "tests/sounds/sample3s.mp3"
9
+
10
+
11
+ def test_invalid_sound_file():
12
+ with pytest.raises(PlaysoundException):
13
+ playsound("invalid.mp3")
14
+
15
+
16
+ def test_non_existent_file():
17
+ with pytest.raises(PlaysoundException):
18
+ playsound("non_existent.mp3")
19
+
20
+
21
+ def test_invalid_backend():
22
+ with pytest.raises(PlaysoundException):
23
+ playsound(valid, backend="invalid_backend")
24
+
25
+
26
+ def test_playsound_from_url():
27
+ url = "https://wrong-url.com/wrong-audio.mp3"
28
+ with pytest.raises(urllib.error.URLError):
29
+ playsound(url)
@@ -0,0 +1,25 @@
1
+ import time
2
+
3
+ from playsound3 import AVAILABLE_BACKENDS, playsound
4
+
5
+ wav = "tests/sounds/звук 音 聲音.wav"
6
+
7
+
8
+ def test_with_blocking():
9
+ for backend in AVAILABLE_BACKENDS:
10
+ print(f"Testing backend: {backend}")
11
+
12
+ sound = playsound(wav, block=True, backend=backend)
13
+ assert not sound.is_alive()
14
+
15
+
16
+ def test_non_blocking():
17
+ for backend in AVAILABLE_BACKENDS:
18
+ print(f"Testing backend: {backend}")
19
+
20
+ sound = playsound(wav, block=False, backend=backend)
21
+ time.sleep(0.05)
22
+ assert sound.is_alive()
23
+
24
+ sound.wait()
25
+ assert not sound.is_alive()
File without changes
File without changes
File without changes