Starting & Stopping Streams

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)
  1. vision.start() creates the stream on the server, connects to the LiveKit room, opens a WebSocket for results, and starts the keepalive loop.
  2. While running, the SDK automatically sends keepalive requests to keep the stream alive and refresh the LiveKit token. Results arrive on the WebSocket.
  3. 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) or on_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:

CauseWhat 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 lostConnection 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 erroronError 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 | null

Displaying 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).