Repository: livekit/agents
File: livekit-plugins/livekit-plugins-soniox/livekit/plugins/soniox/stt.py (Python)

Code before:
            if isinstance(data, rtc.AudioFrame):
                # Get the raw bytes from the audio frame.
                pcm_data = data.data.tobytes()
                self.audio_queue.put_nowait(pcm_data)

    async def _send_audio_task(self) -> None:
        """Take queued audio data and transmit it over the WebSocket."""
        if not self._ws:
            logger.error("WebSocket connection to Soniox Speech-to-Text API is not established")
            return

        while self._ws:
            try:
                data = await self.audio_queue.get()

                if isinstance(data, bytes):
                    await self._ws.send_bytes(data)
                else:
                    await self._ws.send_str(data)
            except asyncio.CancelledError:
                raise
            except Exception as e:
                logger.error(f"Error while sending audio data: {e}")
                break

    async def _recv_messages_task(self) -> None:
        """Receive transcription messages, handle tokens, errors, and dispatch events."""

        # final tokens are accumulated across messages until an endpoint is detected.
        final = _TokenAccumulator()
        is_speaking = False

        def send_endpoint_transcript() -> None:
            nonlocal is_speaking
            if final.text:
                self._event_ch.send_nowait(
                    stt.SpeechEvent(
                        type=SpeechEventType.FINAL_TRANSCRIPT,
                        alternatives=[final.to_speech_data()],
                    )
                )
                self._event_ch.send_nowait(
                    stt.SpeechEvent(
                        type=SpeechEventType.END_OF_SPEECH,
                    )
                )

                # Reset buffers.
                final.reset()

                # Reset speaking state, so the next transcript will send START_OF_SPEECH again.

Review comment (suggestion):
I think we can also update the STT to `aligned_transcript='chunk'` now that it emits timestamps and maybe update the default model from `stt-rt-v3` to `stt-rt-v4` (they already upgrade the model automatically from the server side)

Code after:
            if isinstance(data, rtc.AudioFrame):
                # Get the raw bytes from the audio frame.
                pcm_data = data.data.tobytes()
                self.audio_queue.put_nowait(pcm_data)

    async def _send_audio_task(self) -> None:
        """Take queued audio data and transmit it over the WebSocket."""
        if not self._ws:
            logger.error("WebSocket connection to Soniox Speech-to-Text API is not established")
            return

        while self._ws:
            try:
                data = await self.audio_queue.get()

                if isinstance(data, bytes):
                    await self._ws.send_bytes(data)
                else:
                    await self._ws.send_str(data)
            except asyncio.CancelledError:
                raise
            except Exception as e:
                logger.error(f"Error while sending audio data: {e}")
                break

    async def _recv_messages_task(self) -> None:
        """Receive transcription messages, handle tokens, errors, and dispatch events."""

        # final tokens are accumulated across messages until an endpoint is detected.
        final = _TokenAccumulator()
        is_speaking = False

        def send_endpoint_transcript() -> None:
            nonlocal is_speaking
            if final.text:
                self._event_ch.send_nowait(
                    stt.SpeechEvent(
                        type=SpeechEventType.FINAL_TRANSCRIPT,
                        alternatives=[final.to_speech_data(self.start_time_offset)],
                    )
                )
                self._event_ch.send_nowait(
                    stt.SpeechEvent(
                        type=SpeechEventType.END_OF_SPEECH,
                    )
                )

                # Reset buffers.
                final.reset()

                # Reset speaking state, so the next transcript will send START_OF_SPEECH again.