Video Types
VideoFrame
Video frame data received from the stream.
@dataclass(frozen=True, slots=True)
class VideoFrame:
data: np.ndarray # RGB uint8 array, shape (height, width, 3)
width: int # Frame width in pixels
height: int # Frame height in pixels
timestamp_ms: int # Presentation timestamp in milliseconds
| Property | Type | Description |
|---|
data | np.ndarray | RGB uint8 array with shape (height, width, 3) |
width | int | Frame width in pixels |
height | int | Frame height in pixels |
timestamp_ms | int | Presentation timestamp in milliseconds |
Example usage:
import cv2
from PIL import Image
def on_frame(frame: VideoFrame) -> None:
# OpenCV (note: OpenCV uses BGR)
cv2.imshow("video", cv2.cvtColor(frame.data, cv2.COLOR_RGB2BGR))
# PIL
image = Image.fromarray(frame.data)
# Headless processing
processed = some_ml_model(frame.data)
Recording Types
Recording
Recording data with presigned URLs for a stream.
@dataclass(frozen=True, slots=True)
class Recording:
stream_id: str # Unique stream identifier
video_url: str | None # Presigned URL for video file
events_url: str | None # Presigned URL for events JSON
thumbnail_url: str | None # Presigned URL for thumbnail image
preview_url: str | None # Presigned URL for preview video
frame_count: int | None # Total number of frames
duration_seconds: float | None # Duration in seconds
| Property | Type | Description |
|---|
stream_id | str | Unique stream identifier |
video_url | str | None | Presigned URL for video file (MP4) |
events_url | str | None | Presigned URL for events log (JSONL) |
thumbnail_url | str | None | Presigned URL for thumbnail image (JPEG) |
preview_url | str | None | Presigned URL for preview video (MP4) |
frame_count | int | None | Total number of frames |
duration_seconds | float | None | Duration in seconds |
URLs are valid for a limited time (typically 1 hour).
StreamRecordingInfo
Summary info for a stream recording in a list.
@dataclass(frozen=True, slots=True)
class StreamRecordingInfo:
stream_id: str # Unique stream identifier
width: int # Video width in pixels
height: int # Video height in pixels
started_at: str # ISO 8601 timestamp
ended_at: str | None # ISO 8601 timestamp or None if active
duration_seconds: float | None # Duration in seconds
| Property | Type | Description |
|---|
stream_id | str | Unique stream identifier |
width | int | Video width in pixels |
height | int | Video height in pixels |
started_at | str | ISO 8601 timestamp when stream started |
ended_at | str | None | ISO 8601 timestamp when stream ended |
duration_seconds | float | None | Duration in seconds |
StreamRecordingsList
Paginated list of stream recordings.
@dataclass(frozen=True, slots=True)
class StreamRecordingsList:
recordings: list[StreamRecordingInfo] # List of recording info
total: int # Total recordings available
limit: int # Max per request
offset: int # Recordings skipped
| Property | Type | Description |
|---|
recordings | list[StreamRecordingInfo] | List of recording summaries |
total | int | Total recordings available |
limit | int | Limit used in request |
offset | int | Offset used in request |
Simulate API Types
Simulate API types were added in v1.0.0
ScriptEntry
An entry in a simulation script.
from typing import TypedDict, NotRequired
class StartAction(TypedDict):
prompt: str
image: NotRequired[str | bytes] # Optional image for image-to-video
class InteractAction(TypedDict):
prompt: str
class ScriptEntry(TypedDict):
timestamp_ms: int # When this action occurs (milliseconds from start)
start: NotRequired[StartAction] # Begin a new stream
interact: NotRequired[InteractAction] # Send an interaction
end: NotRequired[dict] # End the stream (empty dict)
| Property | Type | Description |
|---|
timestamp_ms | int | When this action occurs (milliseconds from start) |
start | { prompt: str, image?: str | bytes } | Begin a new stream with initial prompt |
interact | { prompt: str } | Send an interaction prompt |
end | {} | End the current stream (empty dict) |
SimulationStream
Output stream from a simulation job, including recording artifact URLs.
@dataclass(frozen=True, slots=True)
class SimulationStream:
stream_id: str # Unique stream identifier
video_url: str | None # Presigned URL for the video file
events_url: str | None # Presigned URL for the events JSON
thumbnail_url: str | None # Presigned URL for the thumbnail image
preview_url: str | None # Presigned URL for the preview video
frame_count: int | None # Total number of frames
duration_seconds: float | None # Duration of the video in seconds
script_index: int # Index of the script in batch mode (0 for single script)
| Property | Type | Description |
|---|
stream_id | str | Unique stream identifier |
video_url | str | None | Presigned URL for the video file (MP4) |
events_url | str | None | Presigned URL for the events log (JSONL) |
thumbnail_url | str | None | Presigned URL for the thumbnail image (JPEG) |
preview_url | str | None | Presigned URL for the preview video (MP4) |
frame_count | int | None | Total number of frames in the video |
duration_seconds | float | None | Duration of the video in seconds |
script_index | int | Index of the script in batch mode (0 for single script) |
SimulationJobInfo
Summary information for a simulation job in a list.
@dataclass(frozen=True, slots=True)
class SimulationJobInfo:
job_id: str # Unique identifier for the job
status: str # Current job status
priority: str # Job priority
created_at: str # ISO timestamp when job was created
completed_at: str | None # ISO timestamp when job completed
error_message: str | None # Error message if job failed
| Property | Type | Description |
|---|
job_id | str | Unique identifier for the job |
status | str | Current job status (pending, dispatched, processing, completed, failed, cancelled) |
priority | str | Job priority |
created_at | str | ISO timestamp when job was created |
completed_at | str | None | ISO timestamp when job completed |
error_message | str | None | Error message if job failed |
SimulationJobDetail
Detailed information about a simulation job.
@dataclass(frozen=True, slots=True)
class SimulationJobDetail:
job_id: str # Unique identifier for the job
status: SimulationJobStatus # Current job status
priority: str # Job priority
created_at: str # ISO timestamp when job was created
dispatched_at: str | None # ISO timestamp when job was dispatched
started_at: str | None # ISO timestamp when processing started
completed_at: str | None # ISO timestamp when job completed
error_message: str | None # Error message if job failed
assigned_region: str | None # Region where the job is being processed
retry_count: int # Number of times the job has been retried
streams: list[SimulationStream] # Output streams from the simulation
estimated_wait_minutes: float | None # Estimated wait time in minutes
| Property | Type | Description |
|---|
job_id | str | Unique identifier for the job |
status | SimulationJobStatus | Current job status (pending, dispatched, processing, completed, failed, cancelled) |
priority | str | Job priority |
created_at | str | ISO timestamp when job was created |
dispatched_at | str | None | ISO timestamp when the job was dispatched to a worker |
started_at | str | None | ISO timestamp when processing started |
completed_at | str | None | ISO timestamp when job completed |
error_message | str | None | Error message if job failed |
assigned_region | str | None | Region where the job is being processed |
retry_count | int | Number of times the job has been retried |
streams | list[SimulationStream] | Output streams from the simulation |
estimated_wait_minutes | float | None | Estimated wait time in minutes |
SimulationJobsList
Paginated list of simulation jobs.
@dataclass(frozen=True, slots=True)
class SimulationJobsList:
jobs: list[SimulationJobInfo] # List of simulation job summaries
total: int # Total jobs available
limit: int # Limit used in request
offset: int # Offset used in request
| Property | Type | Description |
|---|
jobs | list[SimulationJobInfo] | List of simulation job summaries |
total | int | Total jobs available |
limit | int | Limit used in request |
offset | int | Offset used in request |
Broadcast Types
BroadcastInfo
Broadcast playback URLs for spectators, received via the on_broadcast_ready callback when broadcast=True is passed to start_stream().
@dataclass(frozen=True, slots=True)
class BroadcastInfo:
webrtc_url: str
spectator_token: str
hls_url: str | None = None
| Property | Type | Description |
|---|
webrtc_url | str | WebRTC (WHEP) endpoint URL for spectator playback. |
spectator_token | str | Token required to authenticate a spectator connection. |
hls_url | str | None | Optional HLS playback URL, if available. |
SpectatorConnection
Handle for an active spectator connection. Returned by connect_to_stream().
@dataclass(frozen=True, slots=True)
class SpectatorConnection:
@property
def is_connected(self) -> bool
@property
def peer_connection(self) -> object | None
async def disconnect(self) -> None
| Property | Type | Description |
|---|
is_connected | bool | Indicates whether the spectator connection is currently active. |
peer_connection | Object | None | The underlying RTCPeerConnection. Can be used to retrieve WebRTC statistics (e.g., via getStats()). |
| Method | Description |
|---|
disconnect | Asynchronously close the spectator connection and release resources. |
from odyssey import connect_to_stream
def handle_frame(frame):
cv2.imshow("Broadcast", frame.data)
cv2.waitKey(1)
connection = await connect_to_stream(
webrtc_url="http://localhost:8889/live/stream123",
spectator_token="spectator_abc...",
on_video_frame=handle_frame,
)
# Later
await connection.disconnect()
Status Types
ConnectionStatus
class ConnectionStatus(str, Enum):
AUTHENTICATING = "authenticating" # Authenticating with Odyssey API
CONNECTING = "connecting" # Connecting to signaling server
RECONNECTING = "reconnecting" # Reconnecting after disconnect
CONNECTED = "connected" # Connected and ready
DISCONNECTED = "disconnected" # Disconnected (clean)
FAILED = "failed" # Connection failed (fatal)
Configuration Types
ClientConfig
Configuration for the Odyssey client.
@dataclass
class ClientConfig:
api_key: str # API key for authentication (required)
api_url: str = "https://api.odyssey.ml" # API URL
dev: DevConfig = DevConfig() # Development settings
advanced: AdvancedConfig = AdvancedConfig() # Advanced settings
DevConfig
Development/debug settings.
@dataclass
class DevConfig:
signaling_url: str | None = None # Direct signaling URL (bypasses API)
session_id: str | None = None # Session ID for direct connection
debug: bool = False # Enable debug logging
AdvancedConfig
Advanced connection settings.
@dataclass
class AdvancedConfig:
max_retries: int = 5 # Max retry attempts
initial_retry_delay_ms: int = 1000 # Initial retry delay
max_retry_delay_ms: int = 2000 # Max retry delay
retry_backoff_multiplier: float = 2.0 # Backoff multiplier
queue_timeout_s: int = 30 # Queue timeout in seconds
Exceptions
OdysseyError
Base exception for all Odyssey errors.
class OdysseyError(Exception):
pass
OdysseyAuthError
Raised when authentication fails.
class OdysseyAuthError(OdysseyError):
pass
OdysseyConnectionError
Raised when connection fails.
class OdysseyConnectionError(OdysseyError):
pass
OdysseyStreamError
Raised when a stream operation fails.
class OdysseyStreamError(OdysseyError):
pass
Error Handling
Fatal vs Non-Fatal Errors
The on_error handler receives a fatal boolean parameter:
| Fatal | Description | Action Required |
|---|
True | Connection cannot continue | Reconnect or exit |
False | Recoverable error | May retry or notify user |
Common Errors
| Error | Description |
|---|
OdysseyAuthError | API key is invalid or expired |
OdysseyConnectionError: No streamers available | No streamers available, try again later |
OdysseyConnectionError: Timed out waiting for a streamer | Queue timeout expired |
OdysseyStreamError: Cannot start stream: client is disconnected | Attempted operation while disconnected |
Environment Variables
| Variable | Description |
|---|
ODYSSEY_API_URL | Override default API URL |
ODYSSEY_API_KEY | Default API key (used by examples) |