Starting & Stopping Streams
Understanding how streams work helps you build reliable applications that handle edge cases properly.
Overview
start() → Stream Running → stop()
↑ ↓
keepalive cleanup + final charge
(automatic)vision.start()creates the stream on the server, connects to the LiveKit room, opens a WebSocket for results, and starts the keepalive loop.- While running, the SDK automatically sends keepalive requests to keep the stream alive and refresh the LiveKit token. Results arrive on the WebSocket.
vision.stop()closes the stream on the server (triggering a final billing charge), disconnects from LiveKit, closes the WebSocket, and releases media resources.
Keepalive
Streams have a 45-second TTL. The SDK automatically sends keepalive requests to renew the lease and refresh the LiveKit token before it expires. You don't need to manage this yourself.
If the keepalive fails (network outage, server error), the stream has up to 45 seconds before the server expires it. If a stream expires, you need to call stop() and create a new one.
A 402 error during keepalive means you've run out of credits. This is a terminal error.
Note: Native LiveKit transport handles temporary network disruptions (WiFi to cellular, brief disconnections) automatically. See Transport & Connectivity for details.
Keepalive Retry Behavior
Both SDKs retry failed keepalive requests before giving up:
- Retries: 3 attempts per keepalive cycle
- Backoff: 2 seconds between retries
- On failure: After all retries fail, the stream is closed and
onError(JS) oron_error(Python) is called
If you're building on the low-level API directly, implement similar retry logic — a single transient failure shouldn't kill your stream.
Concurrent Stream Limits
Each API key can run up to 5 concurrent streams. Get your API key at platform.overshoot.ai/api-keys (opens in a new tab). Attempting to create a 6th stream will fail with a 429 error.
// This will fail if you already have 5 streams running
onError: (error) => {
if (error.name === 'ApiError' && error.statusCode === 429) {
console.error('Too many concurrent streams — close one first')
}
}Billing
Streams are billed per second of streaming time, not per inference. The clock starts when you call start() and stops when you call stop() (or when the stream expires).
- Credits are charged at each keepalive interval and at stream teardown
- If you run out of credits mid-stream, the next keepalive will fail with a 402 error and the stream will stop
- Manage your credits at platform.overshoot.ai (opens in a new tab)
Stream Termination
A stream can end for several reasons:
| Cause | What Happens |
|---|---|
You call vision.stop() | Clean shutdown, final charge, resources released |
| Keepalive expires (45s timeout) | Server closes the stream |
| Insufficient credits (402) | Keepalive fails, stream stops |
| Transport connection lost | Connection loss triggers onError, stream stops (native transport auto-reconnects in most cases) |
| User clicks "Stop Sharing" (screen source) | Browser ends the media track, stream stops |
| Server error | onError is called with a ServerError |
After any termination, call stop() to ensure all local resources (media tracks, WebSocket, LiveKit connection) are cleaned up.
Helper Methods
vision.isActive() // boolean
vision.getStreamId() // string | null
vision.getMediaStream() // MediaStream | nullDisplaying a Video Preview
This section applies to the JavaScript SDK in a browser environment. Use getMediaStream() to show the user what's being streamed:
const vision = new RealtimeVision({ /* ... */ })
await vision.start()
const videoElement = document.querySelector('video')
videoElement.srcObject = vision.getMediaStream()
videoElement.play()In the Python SDK, video preview is handled by your source (e.g., OpenCV cv2.imshow).