# MOSS-Live API: Fix Interruptions and Playback Acknowledgements

A voice agent can receive every audio packet correctly and still mishandle a conversation if its playback state gets ahead of what the user heard.

By Paras Madan | 2026-11-09

MOSS-Live 1.0 supports full-duplex conversation, but your client still has to track what actually played. If interruptions fail or replies never finish, start with the microphone stream, playback queue and acknowledgements before changing the prompt.

The [official model page](https://platform.moss.cn/en/docs/models/moss-live/) labels it beta, accepting realtime audio and video and returning audio and text. This is a documentation-based integration guide, not a hands-on assessment of its voice quality or latency.

## Establish a clean audio path first

The [setup guide](https://platform.moss.cn/en/docs/scenarios/moss-live/) uses `wss://api.mosi.cn/v1/realtime?model=moss-live-1.0`, with the API key in the server-side handshake header. Send `attach_session`, wait for `session_attached`, and finish any configuration before streaming. Browser clients should relay through their backend rather than expose the key.

Enable the microphone with a `mic_state` event whose `enabled` field is `true`. Input is 16 kHz mono PCM S16LE, without a WAV header. Keep transmitting at realtime speed, including silence after speech and microphone audio during playback. Output is 48 kHz mono PCM S16LE.

A useful first test is deliberately boring: one microphone, one speaker, no camera and no backend tools. Adding those systems before the audio path works gives you more possible causes for the same symptom. Log capture, receive and playback timestamps separately so a delayed speaker buffer does not look like slow model generation.

## A received chunk has not finished playing

The [protocol reference](https://platform.moss.cn/en/docs/reference/moss-live/) pairs each binary output chunk with `response.audio.context`. Preserve its `run_id`, `turn_id` and `chunk_id`. Send `playback.chunk.completed` when that chunk finishes playing. Send `playback.completed` only after `response.audio.done` has arrived and the reply's audio has drained from the player.

**Acknowledging the queue instead of the speaker creates false conversation history.** A network callback can run well before the listener hears the last word. Put the acknowledgement at the point where your player knows playback has completed, rather than beside the code that receives bytes.

Consider an illustrative three-chunk reply, with each chunk lasting 400 milliseconds. All three arrive quickly, but the user interrupts 150 milliseconds into the second. The player has delivered 550 milliseconds, while the network has delivered 1,200 milliseconds. Treating those two positions as interchangeable loses 650 milliseconds of information about what the user actually heard.

## Stop the right reply, not the whole connection

On `response.audio.stop`, stop the identified reply and discard its queued audio. Acknowledge completed chunks first, then send `playback.stopped`. Its `played_audio_ms` reports only the played portion of the current unfinished chunk, excluding acknowledged chunks and the unplayed queue.

In that example, the first chunk is already acknowledged. The stopped acknowledgement should carry 150 milliseconds, not 550 or 1,200. If the next chunk has not started, its played duration is zero.

Keep the socket open for the next reply. On `response.interrupted`, finish that reply's local state without waiting for `response.ended`. Do not wait for `output.transcript.done` before acknowledging playback either.

Build a regression test around that exact halfway-through-a-chunk case. Also test interruption before playback begins and immediately after a chunk ends. Those boundaries expose bookkeeping errors that a normal, uninterrupted demo will miss. These are proposed test cases, not reported results from running the API.

## Check completion without waiting in a circle

A completion indicator should follow an explicit state transition, rather than a loosely named “done” callback. Keep network delivery, local playback and reply termination as separate states in your application.

For debugging, record the identifiers and each transition without saving raw conversation audio by default. You want to answer a concrete question: did this particular reply stop because the user interrupted it, because the player failed, or because the application is still waiting on an event it no longer needs? A single global `isSpeaking` flag makes that unnecessarily difficult when a new reply overlaps cleanup of the old one.

## MOSS-Live pricing and connection duration

As checked on November 9, the model page says access is free for a limited time, at zero credits. The [public pricing page](https://platform.moss.cn/app/pricing/) does not list a paid MOSS-Live rate. Prices shown for speech generation or transcription belong to different products and should not be used to estimate this endpoint's future price.

The setup guide nevertheless defines the metering interval: from `session_attached` until the connection closes, with settlement afterwards. Muting the microphone is therefore a poor substitute for ending a session. Close the WebSocket and release capture and playback resources when the user ends the call.

For capacity planning, record connected duration alongside useful conversation time. That gives you a basis for a future cost estimate when a paid rate is published, without pretending a temporary free offer is a permanent operating cost.

The integration is ready for a wider trial when a user can interrupt, correct themselves and leave the call without stale speech or a stranded connection. A polished voice cannot compensate for an application that has lost track of the conversation.

I’m Paras. I work at the intersection of AI, engineering and distribution.

I help technical founders turn API details and product decisions into clear technical stories developers can use.

[Book a 30-minute call](https://cal.com/paras-madan/30min).
