import logging import socket import time from queue import Queue from threading import Event from rich.console import Console from speech_to_speech.pipeline.messages import PIPELINE_END from speech_to_speech.pipeline.queue_types import AudioInItem logger = logging.getLogger(__name__) console = Console() # If should_listen stays cleared for longer than this, something in the # pipeline probably failed (LLM exception, TTS crash, etc.). Re-enable # listening so the user isn't permanently locked out. SHOULD_LISTEN_TIMEOUT_S = 30.0 class SocketReceiver: """ Handles reception of the audio packets from the client. """ def __init__( self, stop_event: Event, queue_out: Queue[AudioInItem], should_listen: Event, host: str = "0.0.0.0", port: int = 12345, chunk_size: int = 1024, ) -> None: self.stop_event = stop_event self.queue_out = queue_out self.should_listen = should_listen self.chunk_size = chunk_size self.host = host self.port = port def receive_full_chunk(self, conn: socket.socket, chunk_size: int) -> bytes | None: data = b"" while len(data) < chunk_size: packet = conn.recv(chunk_size - len(data)) if not packet: # connection closed return None data += packet return data def run(self) -> None: self.socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM) self.socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) self.socket.bind((self.host, self.port)) self.socket.listen(1) logger.info("Receiver waiting to be connected...") self.conn, _ = self.socket.accept() logger.info("receiver connected") self.should_listen.set() listen_cleared_at = None while not self.stop_event.is_set(): audio_chunk = self.receive_full_chunk(self.conn, self.chunk_size) if audio_chunk is None: # connection closed self.queue_out.put(PIPELINE_END) break if self.should_listen.is_set(): self.queue_out.put(audio_chunk) listen_cleared_at = None else: # Track how long should_listen has been cleared if listen_cleared_at is None: listen_cleared_at = time.monotonic() elif time.monotonic() - listen_cleared_at > SHOULD_LISTEN_TIMEOUT_S: logger.warning( "should_listen has been cleared for %.0fs — pipeline may be stuck, re-enabling listening", SHOULD_LISTEN_TIMEOUT_S, ) self.should_listen.set() listen_cleared_at = None self.conn.close() logger.info("Receiver closed")