1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144
|
from __future__ import annotations
import time
import uuid
from threading import Thread
from typing import Any
WAIT_TIME: float = 0.02
class PlaysoundException(Exception):
pass
class WmplayerPopen:
"""Popen-like object for Wmplayer backend."""
def __init__(self, sound: str):
self._playing: bool = True
self.thread: Thread = Thread(target=self._play, args=(sound,), daemon=True)
self.thread.start()
def _play(self, sound: str) -> None:
try:
import pythoncom # type: ignore
import win32com.client # type: ignore
except ImportError as e:
raise PlaysoundException("Install 'pywin32' to use the 'wmplayer' backend.") from e
# Create the Windows Media Player COM object
wmp = win32com.client.Dispatch(
"WMPlayer.OCX",
pythoncom.CoInitialize(),
)
wmp.settings.autoStart = True # Ensure playback starts automatically
# Set the URL to your MP3 file
wmp.URL = sound
wmp.controls.play() # Start playback
while wmp.playState != 1 and self._playing: # playState 1 indicates stopped
pythoncom.PumpWaitingMessages() # Process COM events
time.sleep(WAIT_TIME)
wmp.controls.stop()
self._playing = False
def terminate(self) -> None:
self._playing = False
def poll(self) -> int | None:
"""None if sound is playing, integer if not."""
return None if self._playing else 0
def wait(self) -> int:
self.thread.join()
return 0
class WinmmPopen:
"""Popen-like object for Winmm backend."""
def __init__(self, sound: str):
self._playing: bool = True
self.alias: str | None = None
self.thread: Thread = Thread(target=self._play, args=(sound,), daemon=True)
self.thread.start()
def _send_winmm_mci_command(self, command: str) -> str:
try:
import ctypes
except ImportError as e:
raise PlaysoundException("Install 'ctypes' to use the 'winmm' backend") from e
winmm = ctypes.WinDLL("winmm.dll") # type: ignore
buffer = ctypes.create_unicode_buffer(255) # Unicode buffer for wide characters
error_code = winmm.mciSendStringW(ctypes.c_wchar_p(command), buffer, 254, 0)
if error_code:
self._playing = False
raise RuntimeError(f"winmm was not able to play the file! MCI error code: {error_code}")
return buffer.value
def _play(self, sound: str) -> None:
"""Play a sound utilizing windll.winmm."""
# Select a unique alias for the sound
self.alias = str(uuid.uuid4())
self._send_winmm_mci_command(f'open "{sound}" type mpegvideo alias {self.alias}')
self._send_winmm_mci_command(f"play {self.alias}")
while self._playing:
time.sleep(WAIT_TIME)
status = self._send_winmm_mci_command(f"status {self.alias} mode")
if status != "playing":
break
self._send_winmm_mci_command(f"stop {self.alias}")
self._send_winmm_mci_command(f"close {self.alias}")
self._playing = False
def terminate(self) -> None:
self._playing = False
def poll(self) -> int | None:
"""None if sound is playing, integer if not."""
return None if self._playing else 0
def wait(self) -> int:
self.thread.join()
return 0
class AppkitPopen:
"""Popen-like object for AppKit NSSound backend."""
def __init__(self, sound: str):
try:
from AppKit import NSSound # type: ignore
from Foundation import NSURL # type: ignore
except ImportError as e:
raise PlaysoundException("Install 'PyObjC' to use 'appkit' backend.") from e
nsurl: Any = NSURL.fileURLWithPath_(sound)
self._nssound: Any = NSSound.alloc().initWithContentsOfURL_byReference_(nsurl, True)
self._nssound.retain()
self._start_time: float = time.time()
self._nssound.play()
self._duration: float = self._nssound.duration()
def terminate(self) -> None:
self._nssound.stop()
self._duration = time.time() - self._start_time
def poll(self) -> int | None:
"""None if sound is playing, integer if not."""
if time.time() - self._start_time >= self._duration:
return 0
return None
def wait(self) -> int:
while time.time() - self._start_time < self._duration:
time.sleep(WAIT_TIME)
return 0
|