Вот решение, которое я придумал, используя опрос - что никогда не было моим любимым занятием, но winsound
общеизвестно не помогает, когда дело доходит до многопоточности, и я полагаю, что многопоточность - это путь сюда:
def main():
from pathlib import Path
from threading import Event, Thread
from pynput import keyboard
from collections import namedtuple
import wave
skip_sound = Event()
def play_sound_thread(path, max_duration_seconds):
from winsound import PlaySound, SND_ASYNC
PlaySound(path, SND_ASYNC)
elapsed_seconds = 0
poll_interval = 1 / 8
while not skip_sound.is_set() and elapsed_seconds < max_duration_seconds:
skip_sound.wait(poll_interval)
elapsed_seconds += poll_interval
def on_press(key):
if key is keyboard.Key.f9:
nonlocal skip_sound
skip_sound.set()
Wave = namedtuple("Wave", ["path", "file_name", "duration_seconds"])
waves = []
wav_dir = Path("./wavs")
for wav_path in wav_dir.glob("*.wav"):
wav_path_as_str = str(wav_path)
file_name = wav_path.name
with wave.open(wav_path_as_str, "rb") as wave_read:
sample_rate = wave_read.getframerate()
number_of_samples = wave_read.getnframes()
duration_seconds = number_of_samples / sample_rate
wav = Wave(
path=wav_path_as_str,
file_name=file_name,
duration_seconds=duration_seconds
)
waves.append(wav)
listener = keyboard.Listener(on_press=on_press)
listener.start()
for wav in waves:
print(f"Playing {wav.file_name}")
thread = Thread(
target=play_sound_thread,
args=(wav.path, wav.duration_seconds),
daemon=True
)
thread.start()
thread.join()
if skip_sound.is_set():
print(f"Skipping {wav.file_name}")
skip_sound.clear()
return 0
if __name__ == "__main__":
import sys
sys.exit(main())
Как это работает:
Каждый раз, когда необходимо воспроизвести звук, создается новый поток, и в потоке выполняется материал winsound.PlaySound
. PlaySound
получает аргумент SND_ASYNC
, поэтому звук воспроизводится асинхронно, и эта операция сама по себе не блокирует выполнение в потоке. Исполнение потока затем блокируется, однако, циклом, который завершается по истечении определенного промежутка времени (у звука было достаточно времени для завершения воспроизведения) или когда установлено событие skip_sound
.
Обратный вызов on_press
устанавливает событие skip_sound
при нажатии клавиши f9.
Я создал namedtuple
с именем Wave
, который просто должен действовать как простое хранилище данных. Вся необходимая информация, необходимая для любого отдельного волнового файла, содержится в одном из них.
Основной цикл for (второй, первый просто составляет список Wave
объектов), итерируемчерез наши Wave
объекты. Затем мы создаем новый поток для текущего Wave
объекта. Затем мы запускаем поток, чтобы начать воспроизведение звука, и thread.join
так, чтобы основной поток ожидал завершения этого подпотока - в основном, блокируя до завершения подпотока. Когда вызывается обратный вызов on_press
, он преждевременно завершает подпоток, и в результате операция join
больше не будет блокировать, что позволяет нам при необходимости сбросить событие skip_sound
и перейти к следующей итерации. .
Что мне нравится в этом решении:
Что мне не нравится в этом решении:
- Как я уже сказал, функция
play_sound_thread
воспроизводит звук асинхронно, а затем опрашивает, пока не сработает событие skip_sound
или не истечет определенное количество времени. Опрос брутто. Я просто как-то произвольно решил опрашивать с интервалом 1/8 секунды. - Чтобы определить максимальную продолжительность опроса, вам нужно открыть волновой файл, проанализировать его и разделить число. образцов по частоте выборки. Мне кажется грязным открывать один и тот же файл дважды - один раз в
wave.open
и снова в winsound.PlaySound
.