feat: Spatius avatar plugin#5821
Conversation
00544ad to
62653e3
Compare
62653e3 to
5971642
Compare
5971642 to
1aeadcb
Compare
|
@tinalenguyen Hi, sorry for the buzz. I just updated this PR, and resolved all the review and CI checks. It's ready from my point of view, please have a look when you have time :) Thank you so much for your time. |
1aeadcb to
afe1254
Compare
afe1254 to
dc83c03
Compare
dc83c03 to
1adb288
Compare
|
To answer questions in #5014
The initial implementation of our LiveKit integration generates livekit token in Spatius' service, which is not good for security. We've changed it since, now the token generation is in this livekit agents plugin.
I've rebased again, pull from main and bump the versions. For my testing it works. https://docs.spatius.ai/livekit-agents/overview Is the documentation we write to help with the setup (a bit more complicated than video streaming avatar, since we do the rendering on client side). |
|
@tinalenguyen gentle ping on this one β whenever you get a moment. Just wanted to make sure this doesn't get buriedπ |
tinalenguyen
left a comment
There was a problem hiding this comment.
hi, apologies about the delayed review, i left a comment to fix the py.typed file
i also tried it out and the video track was only a black screen, i tested out all the public avatars and i didn't see any changes. could you try to reproduce? i only passed the avatar ID as an argument, ideally the plugin only needs that (or none with a default avatar ID) and works out of the box
| @@ -0,0 +1 @@ | |||
|
|
|||
There was a problem hiding this comment.
Hi Tina, have you tried our livekit demo? https://github.com/spatius-ai/spatius-avatar-demo/tree/main/platform-integrations/livekit-agents-demo/livekit-agents-reference-demo Maybe this can be helpful for your review.
Hi @tinalenguyen. Thanks for reviewing this, to make it clear, it is expected to only receive black frames when attempting to playback the track as video. We actually renders the avatar at client side so itβs motion data (not video frames) we are streaming through livekit. We have a client side rendering SDK and a LiveKit etc adapter that helps to bridge between rtc track and client rendering pipeline. Please checkout the example project and this documentation https://docs.spatius.ai/livekit-agents/client. By doing the rendering on client side, we are able to achieve significantly lower cost and bandwidth requirement. |
|
it's interesting to run the rendering on client side, would like to test it as well. |
longcw
left a comment
There was a problem hiding this comment.
I tested with the frontend in https://github.com/spatius-ai/spatius-avatar-demo/tree/main/platform-integrations/livekit-agents-demo/livekit-agents-reference-demo/frontend.
so basically the plugin needs a specific frontend to show the avatar since the rendering is in the client side. I think it's better to mention this in the plugin's readme and agent example.
|
|
||
| self._agent_session = agent_session | ||
| self._original_audio_output = agent_session.output.audio | ||
| self._original_audio_tail = self._get_audio_sink_proxy_tail(agent_session.output.audio) |
There was a problem hiding this comment.
maybe it's not needed inside the plugin if finding the original tail is for switching it back in aclose.
|
|
||
| segment.pushed_duration += frame.duration | ||
| self._active_req_id = req_id | ||
| self._schedule_active_segment_idle_end() |
There was a problem hiding this comment.
the audio is segmented in like 20ms per frame, this may cancel the task and recreate it per frame? could this be optimized in some other way?
|
I also noticed that the plugin may send multiple playback completed events in a single reply, see the logs below 2026-07-11 10:20:05,725 - DEBUG livekit.plugins.spatius - Spatius avatar first audio frame {"request_id": "20260711022005_xAPSg5JsBpVT", "pid": 28844, "job_id": "AJ_HNQ8eG7Pivit", "room": "voice-agent-room"}
2026-07-11 10:20:07,184 - WARNING livekit.plugins.spatius - Avatar segment end marker missing; forcing finalization {"request_id": "20260711022005_xAPSg5JsBpVT", "idle_timeout": 1.0, "pid": 28844, "job_id": "AJ_HNQ8eG7Pivit", "room": "voice-agent-room"}
2026-07-11 10:20:08,300 - DEBUG livekit.plugins.spatius - Spatius avatar first audio frame {"request_id": "20260711022008_mQlPWLm07f_W", "pid": 28844, "job_id": "AJ_HNQ8eG7Pivit", "room": "voice-agent-room"}
2026-07-11 10:20:09,955 - WARNING livekit.agents - playback_finished called more times than playback segments were captured {"pid": 28844, "job_id": "AJ_HNQ8eG7Pivit", "room": "voice-agent-room"}
2026-07-11 10:20:09,956 - DEBUG livekit.plugins.spatius - Spatius avatar segment playback completed {"request_id": "20260711022000_Ke3EIjJUqJKO", "reason": "timeout", "interrupted": false, "playback_position": 7.392000000000004, "pushed_duration": 7.392000000000004, "pid": 28844, "job_id": "AJ_HNQ8eG7Pivit", "room": "voice-agent-room"}
2026-07-11 10:20:09,956 - WARNING livekit.plugins.spatius - Avatar segment completion timed out, assuming playback finished {"request_id": "20260711022000_Ke3EIjJUqJKO", "timeout": 7.780106067657471, "pid": 28844, "job_id": "AJ_HNQ8eG7Pivit", "room": "voice-agent-room"}
2026-07-11 10:20:10,010 - WARNING livekit.agents - playback_finished called more times than playback segments were captured {"pid": 28844, "job_id": "AJ_HNQ8eG7Pivit", "room": "voice-agent-room"}
2026-07-11 10:20:10,011 - DEBUG livekit.plugins.spatius - Spatius avatar segment playback completed {"request_id": "20260711021957__Exu8cOlOfXJ", "reason": "timeout", "interrupted": false, "playback_position": 10.391999999999998, "pushed_duration": 10.391999999999998, "pid": 28844, "job_id": "AJ_HNQ8eG7Pivit", "room": "voice-agent-room"}not sure if it's from the network connection issue or some race conditions. |
|
Thanks @longcw β I pushed
The one-second implicit EOF is still needed for realtime providers such as Gemini Live that can stop producing audio without sending an explicit EOF. I tested the updated implementation with our Gemini Live end-to-end demo and it is working as expected. |
|
@3DRX I think I see the issue: the error I mentioned above only happens when using a slow non-streaming TTS (e.g. the default openai TTS), where the audio is generated sentence by sentence and there can have 1s delay between sentences. when the delay is greater than but I think it needs to be fixed from the plugin: the framework is making sure one |
|
@longcw Got it, I will test it against the latest Gemini live plugin. I agree that ideally the avatar plugin shouldnβt need to detect implicit audio end. We have this because some users are having the issue regarding Gemini live in livekit agents 1.5 era so we add this as a temporary workaround. |
I tried to remove the ACTIVE_SEGMENT_IDLE_END_SECONDS (1s) logic and test it against various TTS providers, and I find this suitable for most TTS providers but not for gemini live. The problem is when using gemini live, AudioSegmentEnd came in much later than the last audio segment (for example when the user's prompt is "tell me a story", AudioSegmentEnd can be 20+ seconds later than the last audio segment). And by reading the google plugin code, seems like it only close the channel and emitting AudioSegmentEnd until received turn_complete from gemini API, which won't arrive as soon as audio segment ends. Instead, it arrives at estimated playback end time, which is certainly much later then audio generation finished time. Internally, Spatius rely on the EOF marker (end=True) to flush internal audio buffer & for inferencing, so we need an audio segment end signal that's soon after final audio chunk arrives. This means it's incompatable with current gemini live's AudioSegmentEnd sending behavior. |
|
@longcw Seems like generationComplete is a more suitable signal from gemini live API to be used for AudioSegmentEnd. But since this is a behavior change, is it likely to be changed in the google plugin right now? If not, does it make sense for us to detect if gemini live is used in AgentSession and just keep this ACTIVE_SEGMENT_IDLE_END_SECONDS (1s) behavior specifically for gemini live? |
|
Fixed transcription sync by using signal from lk.playback_started and lk.playback_finished, and also defaults to sending PCM audio to spatius. |
Address avatar review feedback: - wire the Spatius SDK on_error/on_close callbacks so a dropped ingress connection tears the session down and releases any pending playback, instead of hanging AgentSession.wait_for_playout() - arm a clear-buffer timeout that finishes the segment locally when the worker never confirms an interrupt with a playback-finished RPC - forward every playback-finished RPC straight to the base AudioOutput, which already does 1:1 segment accounting, and drop the redundant per-segment bookkeeping (_SegmentState, pending deque, finalize lock, interrupted-position estimation) - drop the user_state_changed interrupt; clear_buffer is the sole interrupt signal - validate configuration before super().start() so misconfigurations fail fast without leaving background tasks running - collapse teardown into a single _closed guard and keep a reference to the scheduled aclose task - drop the manual exception-chain unwinding in favor of raise-from
Hi LiveKit team! π
This PR introduces the Spatius avatar plugin.
Working demos:
π Spatius LiveKit Agents Demo
π Product home: https://spatius.ai/
User context:
We had 400+ users on SpatialReal platform, and Spatius AI has already picked up 50+ signups in its first few weeks. Around 70% are developers, and most of them are already building on LiveKit β so having the plugin in the main repo would directly help them discover the integration and offer a new affordable live avatar option.
Happy to address any review feedback or run additional tests.
Thanks for your time!