import asyncio from typing import Optional from fastapi import WebSocket from utils.logger import session_logger from services.interrupt_handler import interrupt_handler from services.tts_error_manager import tts_error_manager from config import settings class AudioSender: def __init__( self, device_id: str, session_id: str, websocket: Optional[WebSocket] = None ): self.device_id = device_id self.session_id = session_id self.session_key = (device_id, session_id) self.websocket = websocket def check_interruption(self): return interrupt_handler.is_interrupted(self.session_key) def is_websocket_connected(self): return ( self.websocket is not None and self.websocket.client_state.name == "CONNECTED" and not getattr(self.websocket, "_closed", False) ) async def send_start_notification(self): if not self.is_websocket_connected(): session_logger.warning( self.device_id, self.session_id, "Session websocket 不存在或会话已中断,无法发送 TTS_START", ) await interrupt_handler.set_interrupt_state(self.session_key, True) return False try: await self.websocket.send_text("TTS_START") session_logger.info( self.device_id, self.session_id, "已发送 TTS_START 给客户端" ) await tts_error_manager.start_tts_session(self.device_id, self.session_id) return True except Exception as e: session_logger.error( self.device_id, self.session_id, f"发送 TTS_START 时发生异常:{e}", exc_info=True, ) await interrupt_handler.set_interrupt_state(self.session_key, True) return False async def send_audio_urls(self, url_queue: asyncio.Queue): try: if not await self.send_start_notification(): return first_url_sent = False first_tts_submit_time = getattr(url_queue, "first_tts_submit_time", None) while True: if self.check_interruption(): session_logger.info( self.device_id, self.session_id, "检测到中断标志,立即停止音频发送任务", ) break try: audio_url = await asyncio.wait_for(url_queue.get(), timeout=0.1) except asyncio.TimeoutError: if self.check_interruption(): break continue except Exception as e: session_logger.error( self.device_id, self.session_id, f"获取音频URL队列数据时出错: {str(e)}", ) break if audio_url is None: break if self.is_websocket_connected() and not self.check_interruption(): try: full_url = f"http://{settings.server_host}:{settings.server_port}/{audio_url}" if not audio_url.startswith('http') else audio_url await self.websocket.send_text(f"TTS_URL:{full_url}") if not first_url_sent and first_tts_submit_time is not None: first_url_sent = True session_logger.info( self.device_id, self.session_id, f"已发送音频URL给客户端: {full_url}", ) except Exception as e: session_logger.error( self.device_id, self.session_id, f"发送音频URL时出错: {str(e)}", ) await interrupt_handler.set_interrupt_state( self.session_key, True ) break else: session_logger.warning( self.device_id, self.session_id, "WebSocket连接已关闭或会话已中断,无法发送音频URL", ) break if self.check_interruption(): break url_queue.task_done() if not self.check_interruption(): try: if self.is_websocket_connected(): try: await self.websocket.send_text("TTS_END") session_logger.info( self.device_id, self.session_id, "已发送 TTS_END 给客户端", ) await tts_error_manager.end_tts_session(self.device_id, self.session_id) except RuntimeError as e: if "Cannot call" in str(e) and "close message" in str(e): session_logger.warning( self.device_id, self.session_id, f"WebSocket已关闭,无法发送TTS_END: {e}", ) else: session_logger.error( self.device_id, self.session_id, f"发送TTSEND时发生运行时错误: {e}", ) else: session_logger.warning( self.device_id, self.session_id, "WebSocket连接已关闭或会话已中断,跳过发送 TTS_END", ) except Exception as e: session_logger.error( self.device_id, self.session_id, f"检查WebSocket状态时出错: {e}" ) except Exception as e: session_logger.error( self.device_id, self.session_id, f"发送音频URL时出错: {e}", exc_info=True, )