如何使用pynput和winsound从回调函数中“继续” for循环?

问题描述 投票:1回答:1

我需要让我的程序播放目录中的wav文件,如果用户按下F9,则跳到下一个文件。我发现我需要将winsound设置为异步模式,以使其在上一个文件仍在播放时跳到下一个文件,但与此同时,我需要运行诸如time.sleep(5)之类的后台进程,否则该程序将立即退出。

我有一个用于循环播放wav文件的for循环,但是我无法直接从on_press()函数中调用continue,因此我尝试使用条件全局变量进行睡眠或不睡眠。因此,我可以使用PlaySound(None,winsound.Purge)停止当前正在播放的wav文件,但无法继续进行for循环的下一次迭代或让我的time.sleep(5)有条件地运行-它将始终运行而且我将始终需要等待这5秒钟才能开始下一次迭代。我使用回调的方式是否有问题,还是应该使用其他输入库或其他内容?

from pynput import keyboard
import winsound
import os

mySkip = 0
currentIdx = 0

def clamp(n, minn, maxn):
    return max(min(maxn, n), minn)

def on_press(key):
    global mySkip
    global currentIdx
    if key == keyboard.Key.f9:
        winsound.Beep(5500, 200)
        mySkip = 1

        currentIdx = clamp(currentIdx + 1, 0, 3)
        winsound.PlaySound(None, winsound.SND_PURGE)

listener = keyboard.Listener(on_press=on_press)
listener.start()

WAVDir = "C:/Users/me/PycharmProjects/projectName/WAV"
wavList = os.listdir(WAVDir) #have 4 files in here, "test0.wav" to "test4.wav"

for idx, i in enumerate(wavList):
    soundPath = WAVDir + "/test" + str(currentIdx) + ".wav"

    print(soundPath)
    winsound.PlaySound(soundPath, winsound.SND_ASYNC)

    if mySkip == 0:
        time.sleep(5)

    currentIdx += 1
    mySkip = 0
python playback
1个回答
1
投票

这是我想到的使用轮询的解决方案-从来都不是我最喜欢做的事,但是众所周知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回调设置按f9键时的skip_sound事件。

我已经创建了一个名为namedtupleWave,它的作用只是像一个简单的数据桶。其中一个文件包含我们需要的任何单个wave文件的所有相关信息。

主for循环(第二个,第一个仅编译Wave对象的列表),我们迭代Wave对象。然后,我们为当前Wave对象产生一个新线程。然后,我们启动线程以开始播放声音,然后单击thread.join,以便主线程等待该子线程完成-基本上阻塞直到子线程完成。触发on_press回调后,它会过早终止子线程,因此join操作将不再处于阻塞状态,允许我们在必要时重置skip_sound事件并继续进行下一个迭代。

我对此解决方案喜欢的东西:

  • 有效
  • 我不喜欢这种解决方案的地方:

  • [就像我说的那样,play_sound_thread功能播放声音异步,然后轮询直到skip_sound事件为触发,或经过一定时间。轮询是总的。我只是随意决定以1/8秒的间隔进行轮询。
  • 为了确定最长轮询时间,您可以需要打开wave文件,对其进行解析,然后除以通过采样率采样。基本上打开相同的东西感觉很脏文件两次-一次在wave.open中,再一次在winsound.PlaySound中。
© www.soinside.com 2019 - 2024. All rights reserved.