Skip to content

feat: migrate realtime to LiveKit#56

Open
VerioN1 wants to merge 5 commits into
mainfrom
alon/livekit
Open

feat: migrate realtime to LiveKit#56
VerioN1 wants to merge 5 commits into
mainfrom
alon/livekit

Conversation

@VerioN1
Copy link
Copy Markdown
Contributor

@VerioN1 VerioN1 commented Jun 3, 2026

Summary

  • Replace the realtime aiortc SDP/ICE media implementation with LiveKit room negotiation and media transport.
  • Keep the existing Python realtime client lifecycle/control API while moving media callbacks and examples to native livekit.rtc tracks and frames.
  • Update subscribe tokens, watch-stream credentials, tests, examples, playground, and realtime dependencies.

Developer Interface: Before / After

Before, callers passed aiortc tracks and received aiortc remote tracks:

from aiortc.contrib.media import MediaPlayer, MediaRecorder
from decart.realtime.types import RealtimeConnectOptions

player = MediaPlayer("input.mp4")
recorder = MediaRecorder("output.mp4")

def on_remote_stream(track):
    recorder.addTrack(track)

client = await RealtimeClient.connect(
    base_url=decart.realtime_base_url,
    api_key=decart.api_key,
    local_track=player.video,
    options=RealtimeConnectOptions(
        model=models.realtime("lucy-restyle-2"),
        on_remote_stream=on_remote_stream,
    ),
)

After, callers publish native LiveKit tracks and consume native LiveKit remote tracks:

from livekit import rtc
from decart.realtime.types import RealtimeConnectOptions

source = rtc.VideoSource(width, height)
local_track = rtc.LocalVideoTrack.create_video_track("input-video", source)

async def push_frames():
    source.capture_frame(
        rtc.VideoFrame(
            width=width,
            height=height,
            type=rtc.VideoBufferType.RGB24,
            data=rgb_frame_bytes,
        )
    )

async def consume_remote(track):
    async for event in rtc.VideoStream(track):
        frame = event.frame

client = await RealtimeClient.connect(
    base_url=decart.realtime_base_url,
    api_key=decart.api_key,
    local_track=local_track,
    options=RealtimeConnectOptions(
        model=models.realtime("lucy-restyle-2"),
        on_remote_stream=lambda track: asyncio.create_task(consume_remote(track)),
        preferred_video_codec="h264",  # default; only "vp9" is also supported
    ),
)

Unchanged developer-facing methods: set, set_prompt, set_image, disconnect, on/off, session_id, subscribe_token, is_connected, and get_connection_state.

Test plan

  • uv sync --all-extras
  • uv run pytest tests/test_realtime_unit.py -v
  • uv run pytest tests/ -v
  • uv run ruff check decart/ tests/ examples/ playground/
  • uv run black --check decart/ tests/ examples/ playground/
  • uv run python -m py_compile examples/realtime_synthetic.py examples/realtime_file.py playground/playground.py

Local testing

  • Install/sync deps: uv sync --all-extras
  • Run unit tests: uv run pytest tests/test_realtime_unit.py -v
  • Run all tests: uv run pytest tests/ -v
  • Try synthetic realtime with an API key: DECART_API_KEY=... uv run python examples/realtime_synthetic.py
  • Try file realtime: DECART_API_KEY=... uv run python examples/realtime_file.py path/to/input.mp4
  • Try camera playground: DECART_API_KEY=... uv run python playground/playground.py --model lucy-restyle-2

Note

High Risk
Breaking change to realtime media types, subscribe token format, and connection protocol; touches core connect/reconnect and credential flows used for all live sessions.

Overview
This PR replaces the realtime media stack from aiortc/WebRTC (SDP offers, ICE, MediaStreamTrack) with LiveKit (rtc.Room, LocalVideoTrack / RemoteVideoTrack). The control channel over WebSocket is largely preserved (prompts, set_image, generation events), but negotiation now uses livekit_join / livekit_room_info (and optional queue_position) instead of offer/answer and ICE messages.

RealtimeClient.connect still exposes the same lifecycle helpers (set_prompt, set_image, disconnect, events), but callers must pass livekit.rtc.LocalVideoTrack and handle RemoteVideoTrack in on_remote_stream. Connect URLs gain livekit_early_room_info=true; preferred_video_codec (h264 / vp9) replaces customize_offer. Session/subscribe tokens are derived from room_name via on_session_started, not legacy session_id + server IP/port.

Subscribe decodes a room-only token, calls POST …/watch-stream/{room} for LiveKit credentials, and joins the room without reusing the stream WebSocket. Legacy subscribe tokens are rejected.

Examples, playground, and decart[realtime] deps switch from aiortc/av to livekit (OpenCV + VideoSource.capture_frame; remote consumption via rtc.VideoStream). Unit tests are refocused on LiveKit connection ordering, codec publish options, and watch-stream HTTP behavior.

Reviewed by Cursor Bugbot for commit e6d7b92. Bugbot is set up for automated code reviews on this repo. Configure here.

Replace the aiortc SDP/ICE media path with LiveKit room negotiation while preserving the realtime client lifecycle and control-message APIs.

Co-authored-by: Cursor <cursoragent@cursor.com>
Comment thread decart/realtime/livekit_connection.py
Comment thread decart/realtime/livekit_manager.py
Default LiveKit publishing to H264 while allowing callers to choose the same codec set exposed by the JS SDK.

Co-authored-by: Cursor <cursoragent@cursor.com>
Comment thread decart/realtime/livekit_connection.py
Comment thread decart/realtime/livekit_connection.py Outdated
Keep H264 as the default LiveKit publish codec and only expose VP9 as the alternate client-selectable codec.

Co-authored-by: Cursor <cursoragent@cursor.com>
Send initial control messages before media publish and correctly report LiveKit reconnecting state.

Co-authored-by: Cursor <cursoragent@cursor.com>
Copy link
Copy Markdown

@cursor cursor Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Cursor Bugbot has reviewed your changes and found 1 potential issue.

Fix All in Cursor

❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, have a team admin enable autofix in the Cursor dashboard.

Reviewed by Cursor Bugbot for commit a18d8ad. Configure here.

Comment thread decart/realtime/livekit_manager.py
Mount the local LiveKit track immediately for no-initial-state sessions, while preserving ack gating for initial image or prompt setup.

Co-authored-by: Cursor <cursoragent@cursor.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants