Files
RKOneAIDebate/audio_player.py
2026-02-24 17:41:04 +08:00

126 lines
3.5 KiB
Python

# audio_player.py
import pyaudio
import threading
import queue
from typing import Optional
_DEFAULTS = dict(
format=pyaudio.paInt16,
channels=2,
rate=44100,
chunk=1024,
)
class _AudioPlayer:
def __init__(self, **kw):
# 用外界传入或缺省值
self.format = kw.get('format', _DEFAULTS['format'])
self.channels = kw.get('channels', _DEFAULTS['channels'])
self.rate = kw.get('rate', _DEFAULTS['rate'])
self.chunk = kw.get('chunk', _DEFAULTS['chunk'])
self._pa = pyaudio.PyAudio()
self._stream = None
self._q = queue.Queue()
self._played = threading.Event()
self._total_feed = 0
self._total_played = 0
self._lock = threading.Lock()
self._thread = threading.Thread(target=self._worker, daemon=True)
self._thread.start()
# ---- 内部工作线程 ----
def _worker(self):
while True:
data = self._q.get()
if data is None:
break
if self._stream is None:
self._stream = self._pa.open(
format=self.format,
channels=self.channels,
rate=self.rate,
output=True,
frames_per_buffer=self.chunk
)
self._stream.write(data)
with self._lock:
self._total_played += len(data)
if self._total_played >= self._total_feed:
self._played.set()
# ---- 对外接口 ----
def feed(self, data: bytes):
if not isinstance(data, bytes):
raise TypeError("feed() 需要 bytes")
with self._lock:
self._total_feed += len(data)
self._played.clear()
self._q.put(data)
def wait(self):
self._played.wait()
def clear(self):
"""清空待播放队列并重置相关状态"""
with self._lock:
# 清空队列
while not self._q.empty():
try:
self._q.get_nowait()
except queue.Empty:
break
# 重置计数状态
unplayed = self._total_feed - self._total_played
self._total_feed = self._total_played # 剩余未播放的都被清空了
if self._total_played >= self._total_feed:
self._played.set()
# ---- 清理 ----
def close(self):
self._q.put(None)
self._thread.join(timeout=1)
if self._stream:
self._stream.close()
self._pa.terminate()
# 模块级变量
_player: Optional[_AudioPlayer] = None
def init(**kw):
"""一次性初始化播放器(必须在首次使用前调用)"""
global _player
if _player is not None:
raise RuntimeError("player 已经初始化过了")
_player = _AudioPlayer(**kw)
def feed(data: bytes):
if _player is None:
raise RuntimeError("请先调用 audio_player.init(...)")
_player.feed(data)
def wait():
if _player is None:
raise RuntimeError("请先调用 audio_player.init(...)")
_player.wait()
def clear():
"""清空待播放的所有数据"""
if _player is None:
raise RuntimeError("请先调用 audio_player.init(...)")
_player.clear()
def close():
global _player
if _player is not None:
_player.close()
_player = None