Use this file to discover all available pages before exploring further.
The Realtime API enables you to transform live video streams with minimal latency using WebRTC. Perfect for building iOS camera effects, video conferencing filters, AR applications, and interactive live streaming.
For iOS and macOS applications, use ephemeral keys instead of embedding your permanent API key in the app bundle. Ephemeral keys are short-lived tokens safe to include in client applications.
Learn more about client tokens and why they’re important for security.
The SDK provides RealtimeCapture for managing camera capture on both iOS and macOS. It handles device selection, format negotiation, and frame rate configuration automatically.
// Toggle between front and back camerastry await capture.switchCamera()// Check current camera positionlet position = capture.position // .front or .back
On macOS, switchCamera() cycles through all available cameras rather than toggling front/back.
Pre-flipping the selfie input keeps server-baked pixels (watermarks, overlays) correctly oriented when you render the remote stream as-is.MirrorMode values:
.off (default) — never mirror.
.auto — mirror when the active camera is .front. Follows switchCamera().
.on — always mirror.
With mirroring enabled, render both the local preview and the remote stream with RTCMLVideoViewWrapper(track:) — no mirror: argument.
referenceImageData - Optional reference image Data
connection (optional) - Connection configuration
iceServers - STUN/TURN server URLs (default: Google STUN)
connectionTimeout - Connection timeout in seconds (default: 15)
rtcConfiguration - Custom RTCConfiguration for advanced WebRTC tuning
media (optional) - Media configuration
video.maxBitrate - Max bitrate in bps (default: 2,500,000)
video.minBitrate - Min bitrate in bps (default: 300,000)
video.maxFramerate - Max framerate (default: 26)
video.preferredCodec - Preferred video codec (default: “VP8”)
Returns:RealtimeMediaStream — the transformed remote stream containing an optional videoTrack you can renderFor image-capable models, pass the reference image data on DecartPrompt:
let imageData = try Data(contentsOf: characterImageURL)let manager = try client.createRealtimeManager( options: RealtimeConfiguration( model: Models.realtime(.lucy_v2v_14b_rt), initialPrompt: DecartPrompt( text: "Substitute the character in the video with the person in the reference image.", referenceImageData: imageData, enrich: true ) ))
Set initialPrompt (with referenceImageData for image-capable models) so the first frame is already transformed — otherwise viewers briefly see the raw camera feed.
Monitor connection state, service status, generation ticks, and session ID using the events AsyncStream:
// Observe state changesTask { for await state in manager.events { switch state.connectionState { case .connecting: showLoadingIndicator() case .connected: hideLoadingIndicator() case .generating: showGeneratingIndicator() case .reconnecting: showReconnectingIndicator() case .disconnected: showReconnectButton() case .idle: break case .error: showError() } // Track generation progress if let tick = state.generationTick { showGenerationTime("\(tick)s") } // Track session ID if let sessionId = state.sessionId { print("Session: \(sessionId)") } // Track queue position if let position = state.queuePosition, let size = state.queueSize { showQueueStatus("Position \(position) of \(size)") } // Track service status switch state.serviceStatus { case .enteringQueue: showQueueMessage() case .ready: hideQueueMessage() case .unknown: break } }}
The SDK automatically reconnects when an unexpected disconnection occurs (e.g., network interruption). During auto-reconnect, the connection state transitions to .reconnecting while the SDK retries with exponential backoff (up to 5 attempts, max 10s delay).When auto-reconnect succeeds, a new RealtimeMediaStream is emitted via remoteStreamUpdates. You must rebind your UI to the new stream’s video track:
Task { for await newRemoteStream in manager.remoteStreamUpdates { // Update your UI with the new video track self.remoteVideoTrack = newRemoteStream.videoTrack }}
Auto-reconnect is not triggered on user-initiated disconnect(), permanent errors (401/403, invalid key, expired session), or after all retries are exhausted. If all retries fail, the state moves to .error.
Replace the video track during an active session (e.g., after switching cameras):
// Create a new video source and tracklet newSource = manager.createVideoSource()let newTrack = manager.createVideoTrack(source: newSource, trackId: "new-video")// Replace the active trackmanager.replaceVideoTrack(with: newTrack)
You can also create audio sources and tracks:
let audioSource = manager.createAudioSource()let audioTrack = manager.createAudioTrack(source: audioSource, trackId: "audio")
RealtimeCapture automatically uses the model’s fps, width, and height properties to configure camera capture. Always pass the model when creating a capture instance.
let model = Models.realtime(.lucy-restyle-2)let capture = RealtimeCapture(model: model, videoSource: videoSource)
Enable prompt enrichment
For best results, set enrich: true to let Decart’s AI enhance your prompts. Only disable it if you need exact prompt control.
Handle auto-reconnect streams
Always observe remoteStreamUpdates to rebind your video track when the SDK auto-reconnects after a network interruption.
Observe state with AsyncStream
Use for await state in manager.events to track connection state, generation ticks, session ID, and queue position in a structured concurrency context.
Clean up properly
Always call manager.disconnect() and capture.stopCapture() when done to avoid memory leaks and unnecessary resource usage.
Test on real devices
Always test camera features on real iOS devices, as the simulator does not support WebRTC camera access.
Request permissions properly
Add camera and microphone usage descriptions to your Info.plist and handle permission denials gracefully in your UI.