Как «продолжить» цикл for из функции обратного вызова с помощью pynput и winsound? - PullRequest
1 голос
/ 24 октября 2019

Мне нужно, чтобы моя программа воспроизводила файлы wav из каталога и переходила к следующему, если пользователь нажимает клавишу F9. Я обнаружил, что мне нужно установить winsound в асинхронный режим, чтобы пропустить его до следующего файла, пока предыдущий файл все еще воспроизводится, но в то же время мне нужно запустить фоновый процесс, например time.sleep (5), илипрограмма немедленно завершится.

У меня есть цикл for для запуска через файлы wav, но я не могу напрямую вызвать continue из функции on_press (), поэтому я попытался использовать условную глобальную переменную для сна или нетспать. Таким образом, я могу остановить текущий воспроизводимый wav-файл с помощью PlaySound (None, winsound.Purge), но я не могу перейти к следующей итерации цикла for или получить условный запуск time.sleep (5) - он всегда будет работатьи мне всегда придется ждать эти 5 секунд, чтобы начать следующую итерацию. Есть ли проблема с тем, как я использую обратный вызов, или я должен использовать другую входную библиотеку или что-то в этом роде?

1 Ответ

2 голосов
/ 25 октября 2019

Вот решение, которое я придумал, используя опрос - что никогда не было моим любимым занятием, но 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.
...