import io import struct import threading import time from typing import Optional, Tuple try: import pygame PYGAME_AVAILABLE = True except ImportError: PYGAME_AVAILABLE = False class AudioPlayer: def __init__(self, wav_data: bytes): self.wav_data = wav_data self.player_thread: Optional[threading.Thread] = None self.stop_event = threading.Event() self.paused_event = threading.Event() self.paused_event.set() self._parse_wav_header() def _parse_wav_header(self): if len(self.wav_data) < 44: self.valid = False return if self.wav_data[0:4] != b'RIFF' or self.wav_data[8:12] != b'WAVE': self.valid = False return fmt_chunk_offset = 12 while fmt_chunk_offset < len(self.wav_data) - 8: chunk_id = self.wav_data[fmt_chunk_offset:fmt_chunk_offset+4] chunk_size = struct.unpack(' bool: return self.valid and PYGAME_AVAILABLE def start(self): if not self.is_valid() or self.player_thread is not None: return self.stop_event.clear() self.paused_event.set() self.player_thread = threading.Thread(target=self._playback_thread) self.player_thread.start() def stop(self): self.stop_event.set() if self.player_thread: self.player_thread.join(timeout=1.0) self.player_thread = None def pause(self): self.paused_event.clear() def resume(self): self.paused_event.set() def seek(self, position: float): pass def _playback_thread(self): try: pygame.mixer.init(frequency=self.sample_rate, size=-self.bits_per_sample, channels=self.num_channels, buffer=4096) sound = pygame.mixer.Sound(buffer=self.audio_data) channel = sound.play() while not self.stop_event.is_set(): self.paused_event.wait() if self.stop_event.is_set(): break if not channel.get_busy(): break time.sleep(0.01) if channel and channel.get_busy(): channel.stop() pygame.mixer.quit() except Exception as e: print(f"Audio playback error: {e}") def get_position(self) -> float: return 0.0 class NullAudioPlayer: def __init__(self, wav_data: bytes): self.wav_data = wav_data def is_valid(self) -> bool: return False def start(self): pass def stop(self): pass def pause(self): pass def resume(self): pass def seek(self, position: float): pass def get_position(self) -> float: return 0.0 def create_audio_player(wav_data: bytes): if PYGAME_AVAILABLE and len(wav_data) >= 44 and wav_data[0:4] == b'RIFF': player = AudioPlayer(wav_data) if player.is_valid(): return player return NullAudioPlayer(wav_data)