You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.10.05 07:27:03