Python Tkinter结合PyAudio开发音频GUI无法从暂停位置续播问题
问题分析
当前代码无法实现续播的核心原因有两点:
- 每次点击播放按钮都会重新打开音频文件、创建全新的PyAudio流,wave对象每次都会从文件头开始读取帧,自然无法记住暂停位置
- 暂停逻辑触发后直接关闭了流和文件,没有保留当前播放的帧位置
具体修改方案
1. 新增类全局变量(在__init__方法中更新)
# class globals self.stream_paused = False self.stream_initialized = False # 标记流是否已经初始化 self.current_frame = 0 # 记录当前播放到的帧位置 self.wf = None # 全局wave文件对象 self.pyaudio_init = pyaudio.PyAudio() # 全局PyAudio实例,避免重复创建
2. 重写播放按钮逻辑
不要每次点击播放都开启新线程、重新打开文件,仅在首次播放/播放完成后才初始化流:
def buttons_command_play(self): ''' 播放/续播逻辑 ''' if self.stream_initialized and self.stream_paused: # 处于暂停状态,直接续播 self.stream_paused = False self.stream.start_stream() # 启动后台监听线程 threading.Thread(target=self.monitor_stream, daemon=True).start() elif not self.stream_initialized: # 首次播放,初始化流 self.stream_paused = False self.current_frame = 0 self.wf = wave.open(self.audio_file, mode = 'rb') # 定义回调 def callback(in_data, frame_count, time_info, status): # 从当前帧位置开始读 self.wf.setpos(self.current_frame) data = self.wf.readframes(frame_count) # 更新当前帧位置 self.current_frame += frame_count # 读到文件末尾就结束 if len(data) == 0: self.stream_initialized = False return (data, pyaudio.paComplete) return (data, pyaudio.paContinue) # 打开流 self.stream = self.pyaudio_init.open(format=self.pyaudio_init.get_format_from_width(self.wf.getsampwidth()), input = False, channels=self.wf.getnchannels(), rate=self.wf.getframerate(), output=True, stream_callback=callback) self.stream_initialized = True self.stream.start_stream() # 启动监听线程 threading.Thread(target=self.monitor_stream, daemon=True).start()
3. 新增流监听方法,替换原play_audio里的循环逻辑
def monitor_stream(self): ''' 监听流状态,暂停/结束时处理资源 ''' while self.stream.is_active() and not self.stream_paused: time.sleep(0.1) self.stream.stop_stream() # 只有播放完成才关闭文件和流,暂停时不关闭保留进度 if not self.stream_paused: self.stream.close() self.wf.close() self.stream_initialized = False self.current_frame = 0
4. 简化暂停逻辑
def buttons_command_playback_pause(self): ''' 暂停音频 ''' if self.stream_initialized and not self.stream_paused: self.stream_paused = True
进度条实现说明
不需要调用PyAudio.get_time()获取时间,直接用self.current_frame / self.wf.getframerate()就能得到准确的当前播放秒数,总时长为self.wf.getnframes() / self.wf.getframerate(),直接用这两个值更新Matplotlib里的进度条位置即可。
完整可运行代码
import tkinter as tk from tkinter import ttk import wave import pyaudio import threading import time import numpy as np import datetime from matplotlib.figure import Figure from matplotlib.backends.backend_tkagg import FigureCanvasTkAgg # gui class class basic_player(): def __init__(self, root): # BUILD ROOT self.root = root root.title('Playback') self.audio_file = '替换为你的本地WAV文件路径' self.frame_plot() self.frame_buttons() # class globals self.stream_paused = False self.stream_initialized = False # 标记流是否已经初始化 self.current_frame = 0 # 记录当前播放到的帧位置 self.wf = None # 全局wave文件对象 self.pyaudio_init = pyaudio.PyAudio() # 全局PyAudio实例,不要重复创建 def frame_plot(self): '''Frame for file plot''' self.frame_plot = ttk.Frame(self.root, height = 100, width = 500) self.frame_plot.grid(column = 0, row = 0, sticky = 'nsew', columnspan = 2) self.frame_plot.grid_propagate(False) # plot file self.func_plot_fileplot() def func_plot_fileplot(self): '''Plot the main audiofile''' # create figure to contain plot # get frame size parameters (update frame parameters first) self.frame_plot.update() dpi = self.root.winfo_fpixels('1i') plotwidth = self.frame_plot.winfo_width() / dpi plotheight = self.frame_plot.winfo_height() / dpi # create plot plot_figure_fileplot_main = Figure(figsize = (plotwidth, plotheight), dpi = dpi, frameon = False, tight_layout = True) # get data with wave.open(self.audio_file, mode = 'rb') as wf: infile_audio_bytes = wf.readframes(wf.getnframes()) data = np.frombuffer(infile_audio_bytes, dtype = np.int16) # plot x labels lst_x_ticks = list(range(0, wf.getnframes(), int(wf.getnframes() / 8))) + [wf.getnframes()] lst_x_label = [str(datetime.timedelta(seconds = int(sample / wf.getframerate()))) for sample in lst_x_ticks] # add subplot plot_figure_fileplot = plot_figure_fileplot_main.add_subplot(111) plot_figure_fileplot.plot(data, linewidth = 0.25) # adjust subplot visuals plot_figure_fileplot.set_xmargin(0) plot_figure_fileplot.yaxis.set_visible(False) plot_figure_fileplot.spines['top'].set_visible(False) plot_figure_fileplot.spines['right'].set_visible(False) plot_figure_fileplot.spines['left'].set_visible(False) # labels for plot x axis plot_figure_fileplot.set_xticks(lst_x_ticks) # set x labels to existing to make sure they find the right spot plot_figure_fileplot.set_xticklabels(lst_x_label, size = 8) #create tkinter canvas self.canvas_plot_figure_main = FigureCanvasTkAgg(plot_figure_fileplot_main, master = self.frame_plot) self.canvas_plot_figure_main.draw() #place canvas on tkinter window self.canvas_plot_figure_main.get_tk_widget().grid(sticky = 'nsew') def frame_buttons(self): '''The main frame for the initial window''' frame_buttons = ttk.Frame(self.root, width = 100) frame_buttons.grid(column = 0, row = 1, sticky = 'nsew') btn_play = tk.Button(frame_buttons, text = 'PLAY', command = self.buttons_command_play, state = 'normal', width = 10) btn_play.grid(column = 0, row = 0, sticky = 'nsew', padx = 10, pady = 10) btn_pause = tk.Button(frame_buttons, text = 'PAUSE', command = self.buttons_command_playback_pause, state = 'normal', width = 10) btn_pause.grid(column = 1, row = 0, sticky = 'nsew', padx = 10, pady = 10) def buttons_command_play(self): ''' 播放/续播逻辑 ''' if self.stream_initialized and self.stream_paused: # 处于暂停状态,直接续播 self.stream_paused = False self.stream.start_stream() # 启动后台监听线程 threading.Thread(target=self.monitor_stream, daemon=True).start() elif not self.stream_initialized: # 首次播放,初始化流 self.stream_paused = False self.current_frame = 0 self.wf = wave.open(self.audio_file, mode = 'rb') # 定义回调 def callback(in_data, frame_count, time_info, status): # 从当前帧位置开始读 self.wf.setpos(self.current_frame) data = self.wf.readframes(frame_count) # 更新当前帧位置 self.current_frame += frame_count # 读到文件末尾就结束 if len(data) == 0: self.stream_initialized = False return (data, pyaudio.paComplete) return (data, pyaudio.paContinue) # 打开流 self.stream = self.pyaudio_init.open(format=self.pyaudio_init.get_format_from_width(self.wf.getsampwidth()), input = False, channels=self.wf.getnchannels(), rate=self.wf.getframerate(), output=True, stream_callback=callback) self.stream_initialized = True self.stream.start_stream() # 启动监听线程 threading.Thread(target=self.monitor_stream, daemon=True).start() def monitor_stream(self): ''' 监听流状态,暂停/结束时处理资源 ''' while self.stream.is_active() and not self.stream_paused: time.sleep(0.1) self.stream.stop_stream() # 只有播放完成才关闭文件和流,暂停时不关闭 if not self.stream_paused: self.stream.close() self.wf.close() self.stream_initialized = False self.current_frame = 0 def buttons_command_playback_pause(self): ''' 暂停音频 ''' if self.stream_initialized and not self.stream_paused: self.stream_paused = True ## SETUP AND RUN root = tk.Tk() basic_player(root) root.mainloop()
内容的提问来源于stack exchange,提问作者Lachlan
相关产品推荐
相关产品推荐

