Streams and presence

Media sessions of a Space and presence (who is connected, and their cursors).

Open a media session for another client, stream frames to callbacks, and join presence. PresenceView and the presence_cursor_art* functions draw the cursors the same way in every Cua UI.

Space streams#

Methods of Space.

Space.open_stream#

Mints a media session (ticket + WebSocket URL) for another client to attach to.

async def open_stream(self, options: SpaceStreamOptions) -> SpaceStreamTicket
ParameterTypeDefault
optionsSpaceStreamOptionsrequired

Returns SpaceStreamTicket · Async · Raises CuaError

Space.attach_stream#

Attaches to a ticket from Self.open_stream and delivers the media socket's encoded frames and audio packets to the sinks as they arrive. Keyframe gating, loss recovery and decoding are the caller's; Self.stream_session does them and ships with Cua Spaces. End the session with Self.close_stream and the ticket's media_session_id.

async def attach_stream(self, ticket: SpaceStreamTicket, frames: FrameSink, audio: Optional[AudioSink]) -> MediaSession
ParameterTypeDefault
ticketSpaceStreamTicketrequired
framesFrameSinkrequired
audioOption<AudioSink>required

Returns MediaSession · Async · Raises CuaError

Space.stream_session#

Opens a media session and delivers keyframe-gated encoded frames and audio to the sinks (one delivery thread; decoding stays with the caller). The streaming client ships with Cua Spaces (source-available, FSL-1.1-MIT): without it this raises HostCapabilityMissing; Self.attach_stream needs no client.

async def stream_session(self, options: SpaceStreamOptions, frames: FrameSink, audio: Optional[AudioSink]) -> SpaceStreamSession
ParameterTypeDefault
optionsSpaceStreamOptionsrequired
framesFrameSinkrequired
audioOption<AudioSink>required

Returns SpaceStreamSession · Async · Raises CuaError (HostCapabilityMissing)

Space.close_stream#

Closes a media session opened with Space.open_stream.

async def close_stream(self, media_session_id: str) -> None
ParameterTypeDefault
media_session_idStringrequired

Async · Raises CuaError

Space.websocket_headers#

Headers a media WebSocket to this Space needs besides its ticket.

async def websocket_headers(self) -> List[HttpHeader]

Returns Vec<HttpHeader> · Async · Raises CuaError

Space.windows#

Streamable windows, optionally of one app.

async def windows(self, app: Optional[str]) -> List[SpaceWindow]
ParameterTypeDefault
appOption<String>required

Returns Vec<SpaceWindow> · Async · Raises CuaError

SpaceStreamSession#

A live Space media session (see Space.stream_session).

Returned by Space.stream_session.

MethodDescription
closeCloses the socket and the media session.
request_keyframeAsks the encoder for a keyframe.
send_textSends a raw JSON control message (for example input events).
AccessorReturnsDescription
codec()StringNegotiated codec.
is_open()boolWhether the socket is still open.
media_session_id()StringMedia session id.
stats()SpaceStreamStatsDelivery counters (zeros once closed).

SpaceStreamSession.close#

Closes the socket and the media session. Returns the final counters.

async def close(self) -> SpaceStreamStats

Returns SpaceStreamStats · Async · Raises CuaError

SpaceStreamSession.request_keyframe#

Asks the encoder for a keyframe.

def request_keyframe(self) -> None

Raises CuaError

SpaceStreamSession.send_text#

Sends a raw JSON control message (for example input events).

def send_text(self, json: str) -> None
ParameterTypeDefault
jsonStringrequired

Raises CuaError

SpaceStreamOptions record#

What to stream and how.

FieldTypeDefaultDescription
window_id / windowIdOption<String>NoneWindow handle (wins over app_name and display).
app_name / appNameOption<String>NoneThe largest window of this app.
displayOption<String>NoneDisplay id (None = primary).
codecsVec<String>[]Codecs in preference order (h264, bgra, png; empty = any).
max_fps / maxFpsu320FPS cap (0 = 30).
max_dimension / maxDimensionu320Long-edge cap (0 = native).
audioboolfalseRequest the paired audio track.
ticket_ttl_ms / ticketTtlMsOption<u64>NoneTicket lifetime (default 60 s).
policyOption<String>NoneInput policy: view_only (default), background_only (input without activating the target) or allow_activation.

SpaceStreamTicket record#

A minted media session.

Returned by Space.open_stream.

FieldTypeDefaultDescription
spaceStringSpace id.
media_session_id / mediaSessionIdStringMedia session id.
ws_url / wsUrlStringMedia WebSocket URL, ticket included (the daemon passthrough in daemon mode).
ticketStringThe bare ticket.
ticket_expires_at / ticketExpiresAtOption<String>Expiry (RFC 3339).
codecStringh264, bgra or png.
wire_version / wireVersionu32Media wire version.
widthu32Initial width.
heightu32Initial height.
needs_headers / needsHeadersboolAttaching needs Space.websocket_headers (a gateway or the daemon passthrough).

SpaceStreamStats record#

Delivery counters of a SpaceStreamSession.

Returned by SpaceStreamSession.close, SpaceStreamSession.stats.

FieldTypeDefaultDescription
framesu64Frames delivered.
keyframesu64Keyframes delivered.
frames_dropped / framesDroppedu64Frames dropped (sink behind; each forces a keyframe resync).
frames_gated / framesGatedu64Frames held back awaiting a keyframe.
keyframe_requests / keyframeRequestsu64Keyframe requests sent.
audio_packets / audioPacketsu64Audio packets delivered.
audio_lost / audioLostu64Audio packets reported lost.
eventsu64Control events delivered.
malformedu64Malformed messages ignored.

SpacePresence#

A joined presence session.

Returned by Space.join_presence, Space.join_presence_for.

MethodDescription
leaveLeaves.
meThis participant.
next_eventThe next event, or None when timeout_ms (default 5 s) passes.
rosterEveryone present, with cursors when known.
update_cursorMoves this participant's cursor.
AccessorReturnsDescription
uses_datagrams()boolWhether cursors travel over the QUIC datagram channel (else the Join stream and UpdateCursor).
view()PresenceViewA fresh PresenceView seeded with the caller and the roster at join, with the render delay matching the transport. Feed it every event from next_event.

SpacePresence.leave#

Leaves.

async def leave(self) -> None

Async · Raises CuaError

SpacePresence.me#

This participant.

async def me(self) -> PresenceParticipant

Returns PresenceParticipant · Async · Raises CuaError

SpacePresence.next_event#

The next event, or None when timeout_ms (default 5 s) passes.

async def next_event(self, timeout_ms: Optional[int]) -> Optional[PresenceEvent]
ParameterTypeDefault
timeout_msOption<u64>required

Returns Option<PresenceEvent> · Async · Raises CuaError

SpacePresence.roster#

Everyone present, with cursors when known.

async def roster(self) -> List[PresenceMember]

Returns Vec<PresenceMember> · Async · Raises CuaError

SpacePresence.update_cursor#

Moves this participant's cursor. Throttled to 30 Hz, newest wins: a move inside the interval is held and sent at its end, and show, hide and target changes go out at once. Never waits for next_event.

async def update_cursor(self, cursor: PresenceCursor) -> None
ParameterTypeDefault
cursorPresenceCursorrequired

Async · Raises CuaError

PresenceIdentity record#

Who joins presence.

FieldTypeDefaultDescription
idStringStable principal id.
display_name / displayNameStringDisplay name.
colorString""Requested color.
agentboolfalseAn agent rather than a human.

PresenceParticipant record#

A presence participant.

Returned by PresenceView.participant, SpacePresence.me.

FieldTypeDefaultDescription
participant_id / participantIdStringParticipant id (per join).
principal_id / principalIdStringPrincipal id.
display_name / displayNameStringDisplay name.
colorStringColor.
kindStringhuman or agent.

PresenceMember record#

A roster entry.

Returned by SpacePresence.roster.

FieldTypeDefaultDescription
participantPresenceParticipantWho.
cursorOption<PresenceCursor>Their cursor, if known.

PresenceCursor record#

A normalized cursor.

FieldTypeDefaultDescription
display_id / displayIdString""Display id (empty = primary).
window_id / windowIdOption<String>NoneWindow handle.
xf64X in [0, 1].
yf64Y in [0, 1].
visiblebooltrueVisible.
pressedboolfalseA button is down (datagram channel only).
shapeString"arrow"The guest's cursor shape here (server-computed): arrow, text, pointer, resize_ns, resize_ew, resize_nesw, resize_nwse, wait, progress, not_allowed, crosshair, grab, grabbing, move. Ignored when publishing.
shape_source / shapeSourceString"unspecified"How shape was determined: unspecified, hit_test, system or probe. Ignored when publishing.
at_ms / atMsf640.0Server time of the sample in milliseconds (0 = unknown).
received_ms / receivedMsf640.0Local Unix time it was received in milliseconds (0 = unknown).

PresenceEvent record#

A presence event.

Returned by SpacePresence.next_event.

FieldTypeDefaultDescription
kindStringjoined, left, cursor_moved, shape_changed, heartbeat or keep_alive.
participantOption<PresenceParticipant>joined: who.
participant_id / participantIdOption<String>left / cursor_moved / shape_changed: whose.
cursorOption<PresenceCursor>cursor_moved: where.
shapeOption<String>Noneshape_changed: the new shape.
shape_source / shapeSourceOption<String>Noneshape_changed: how it was determined.
reasonOption<String>Noneleft: why (left, disconnected, timeout, run_ended, or empty).
participant_ids / participantIdsOption<Vec<String>>Noneheartbeat: everyone present.

PresenceView#

The shared presence netcode model: who is present and what to draw. Remote cursors are interpolated (render delay, Catmull-Rom, extrapolation cap), idle cursors faded, stale participants dropped and your own cursor kept at the local pointer (libs/cua/proto/PRESENCE.md section 4). Feed it every presence event; call drawables each frame.

Returned by SpacePresence.view.

MethodDescription
newA view for me (the caller's participant id), rendering remote cursors delay_ms behind (default 100; 66 on the datagram channel).
applyFolds one event in at local_ms (see presence_now_ms).
drawablesEverything to draw at local_ms, your own cursor at pointer when the local pointer is over the surface.
expireDrops everyone but the caller when heartbeats stopped for 3 intervals.
participantA participant.
set_heartbeat_interval_msThe expected heartbeat interval (default 5000 ms).
shape_ofA participant's current shape name (the caller's included).
upsertAdds or updates a participant.
AccessorReturnsDescription
participant_ids()Vec<String>Participant ids, the caller first.

PresenceView.new#

A view for me (the caller's participant id), rendering remote cursors delay_ms behind (default 100; 66 on the datagram channel).

PresenceView(me: str, delay_ms: Optional[float] = None)
ParameterTypeDefault
meStringrequired
delay_msOption<f64>None

Returns PresenceView

PresenceView.apply#

Folds one event in at local_ms (see presence_now_ms). Returns whether anything changed.

def apply(self, event: PresenceEvent, local_ms: float) -> bool
ParameterTypeDefault
eventPresenceEventrequired
local_msf64required

Returns bool

PresenceView.drawables#

Everything to draw at local_ms, your own cursor at pointer when the local pointer is over the surface.

def drawables(self, local_ms: float, pointer: Optional[PresencePoint] = None) -> List[PresenceDrawable]
ParameterTypeDefault
local_msf64required
pointerOption<PresencePoint>None

Returns Vec<PresenceDrawable>

PresenceView.expire#

Drops everyone but the caller when heartbeats stopped for 3 intervals. Returns the removed ids.

def expire(self, local_ms: float) -> List[str]
ParameterTypeDefault
local_msf64required

Returns Vec<String>

PresenceView.participant#

A participant.

def participant(self, participant_id: str) -> Optional[PresenceParticipant]
ParameterTypeDefault
participant_idStringrequired

Returns Option<PresenceParticipant>

PresenceView.set_heartbeat_interval_ms#

The expected heartbeat interval (default 5000 ms).

def set_heartbeat_interval_ms(self, ms: float) -> None
ParameterTypeDefault
msf64required

PresenceView.shape_of#

A participant's current shape name (the caller's included).

def shape_of(self, participant_id: str) -> Optional[str]
ParameterTypeDefault
participant_idStringrequired

Returns Option<String>

PresenceView.upsert#

Adds or updates a participant.

def upsert(self, participant: PresenceParticipant) -> None
ParameterTypeDefault
participantPresenceParticipantrequired

PresenceDrawable record#

One cursor to draw now (see PresenceView.drawables).

Returned by PresenceView.drawables.

FieldTypeDefaultDescription
participant_id / participantIdStringWhose.
display_name / displayNameStringTheir display name.
colorStringTheir color (#rrggbb).
is_me / isMeboolThis client's own cursor, drawn at the local pointer.
is_agent / isAgentboolAn agent.
xf64Normalized x.
yf64Normalized y.
shapeStringShape name (see PresenceCursor.shape).
shape_source / shapeSourceStringHow the shape was determined.
alphaf64Opacity in [0, 1] (idle cursors fade).
display_id / displayIdStringDisplay id of the cursor's target.
window_id / windowIdOption<String>Window id, when over a window stream.

PresencePoint record#

A normalized point.

FieldTypeDefaultDescription
xf64X in [0, 1].
yf64Y in [0, 1].

CursorArt record#

One cursor shape's shared art: an SVG path on a square canvas, filled with the participant's color over an outline.

Returned by presence_cursor_art, presence_cursor_art_all.

FieldTypeDefaultDescription
shapeStringShape name.
path_d / pathDStringSVG path data (absolute M, L, C, Z; nonzero fill).
hotspot_x / hotspotXf64Hot spot x on the canvas.
hotspot_y / hotspotYf64Hot spot y on the canvas.
canvasf64Canvas size (square).
outline_color / outlineColorStringOutline color, drawn under the fill.
outline_width / outlineWidthf64Outline width in canvas units.

presence_color#

The stable presence color of an agent or other principal (#rrggbb): a cursor palette color picked by a hash of id. Request it when the agent joins presence (PresenceIdentity.color) and use it as the agent's avatar background, so both come from one source.

def presence_color(id: str) -> str
ParameterTypeDefault
idStringrequired

Returns String

presence_text_color#

Black or white text (#000000 / #ffffff), whichever reads better on background (#rrggbb).

def presence_text_color(background: str) -> str
ParameterTypeDefault
backgroundStringrequired

Returns String

presence_now_ms#

The local clock PresenceView expects: Unix time in milliseconds.

def presence_now_ms() -> float

Returns f64

presence_cursor_art#

The shared art for shape (unknown names give the arrow).

def presence_cursor_art(shape: str) -> CursorArt
ParameterTypeDefault
shapeStringrequired

Returns CursorArt

presence_cursor_art_all#

The shared art for every shape.

def presence_cursor_art_all() -> List[CursorArt]

Returns Vec<CursorArt>

presence_cursor_art_svg#

A standalone SVG of shape in color, size pixels square.

def presence_cursor_art_svg(shape: str, color: str, size: float) -> str
ParameterTypeDefault
shapeStringrequired
colorStringrequired
sizef64required

Returns String

presence_conformance_json#

The presence conformance vectors (JSON), for binding test suites.

def presence_conformance_json() -> str

Returns String