我需要让我的程序播放目录中的wav文件,如果用户按下F9,则跳到下一个目录。我发现我需要将winsound设置为异步模式,以使其在上一个文件仍在播放时跳至下一个文件,但与此同时,我需要运行诸如time.sleep(5)之类的后台进程,否则该程序将立即退出。
我有一个for循环来运行wav文件,但是我无法直接从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
答案 0 :(得分:2)
这是我想到的使用轮询的解决方案-从来都不是我最喜欢做的事情,但是众所周知,winsound
在线程化方面并不合作,我相信线程化是解决问题的方法:< / p>
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
事件时终止。 >
当按下f9键时,on_press
回调会设置skip_sound
事件。
我创建了一个名为namedtuple
的{{1}},它的作用就像一个简单的数据桶。其中一个文件包含我们需要的任何单个wave文件的所有相关信息。
主要的for循环(第二个,第一个仅编译Wave
对象的列表),我们遍历Wave
对象。然后,我们为当前Wave
对象生成一个新线程。然后,我们启动线程以开始播放声音,并Wave
使其开始播放,以便主线程等待该子线程完成-基本上阻塞直到子线程完成。触发thread.join
回调后,它会过早终止子线程,结果on_press
操作将不再处于阻塞状态,允许我们在必要时重置join
事件,并继续进行下一个迭代。
我喜欢这种解决方案的地方:
我不喜欢这种解决方案的地方:
skip_sound
函数会播放声音
异步,然后轮询直到play_sound_thread
事件
触发,或经过一定时间。轮询是总的。我只是随意决定以1/8秒的间隔进行轮询。skip_sound
中,一次在wave.open
中。