Selkies
Developer Referenceselkies

_VideoRelay

Bounded video delivery for one (client, display) pair.

The fan-out offers every encoded chunk synchronously and never awaits a socket; each relay's own task drains its backlog. One slow client can therefore neither pace the other clients nor back frames up into the shared pipeline or its socket transport (whose freed burst peaks the allocator retains, ratcheting RSS): past its byte budget (~ VIDEO_RELAY_BUDGET_SECONDS of stream at the configured bitrate) it drops its backlog and skips ahead to the next keyframe, the standard broadcast-video contract. Keyframes are exempt from the budget (part of one is useless), so the true bound is budget plus one keyframe burst.

H.264 chain safety is tracked per stripe ROW (wire-header y_start, bytes 4:6): one capture frame can mix IDR and delta stripes (a lone stripe encoder re-init IDRs only its own row), so after any drop a row's delta chunks stay gated until that row's own IDR arrives — a delivered delta otherwise decodes against a reference the client never received. The wire type byte (offset 1) is stamped from the encoder's ACTUAL output picture type on every backend, and a requested recovery IDR covers every row (force_idr_all), so gated rows converge on the next request. JPEG chunks (0x03) have no reference chain: never gated, and a drop only costs a repaint request. A fresh relay starts fully gated, so a joining client waits for a keyframe instead of decoding mid-GOP garbage.

Attributes

attribute__slots__
= ('server', 'display_id', 'ws', 'budget', 'backlog', 'backlog_bytes', 'live_rows', 'stopped', '_wake', '_task', '_next_sync_req')
attributeserver
= server
attributedisplay_id
= display_id
attributews
= ws
attributebudget
= budget

Skip-ahead byte bound for the backlog (_video_relay_budget).

attributebacklogdeque
= deque()

Undrained fan-out items, oldest first.

attributebacklog_bytes
= 0

Payload bytes held in backlog.

attributelive_rowsset[int]
= set()

Stripe rows whose IDR was accepted into the current backlog; only their delta chunks are chain-continuous for this client.

attributestopped
= False

Set by stop; the drain task exits after its in-flight send.

attribute_wake
= asyncio.Event()
attribute_taskOptional[asyncio.Task]
= None
attribute_next_sync_req
= 0.0

Functions

func__init__(self, server, display_id, ws, budget) -> None
Source Code
def __init__(self, server: "DataStreamingServer", display_id: str,
             ws: web.WebSocketResponse, budget: int) -> None:
    self.server = server
    self.display_id = display_id
    self.ws = ws
    self.budget = budget
    self.backlog: deque = deque()
    self.backlog_bytes = 0
    self.live_rows: set[int] = set()
    self.stopped = False
    self._wake = asyncio.Event()
    self._task: Optional[asyncio.Task] = None
    self._next_sync_req = 0.0
paramself
paramserverDataStreamingServer
paramdisplay_idstr
paramwsweb.WebSocketResponse
parambudgetint

Returns

None
funcstart(self) -> None
Source Code
def start(self) -> None:
    self._task = asyncio.create_task(
        self._run(), name=f"VideoRelay:{self.display_id}")
paramself

Returns

None
funcstop(self) -> None

Graceful: an in-flight send completes — cancelling mid-frame would tear the websocket framing on a socket that stays open for control.

Source Code
def stop(self) -> None:
    """Graceful: an in-flight send completes — cancelling mid-frame would
    tear the websocket framing on a socket that stays open for control."""
    self.stopped = True
    self.backlog.clear()
    self.backlog_bytes = 0
    self._wake.set()
paramself

Returns

None
funcflush_for_gate(self) -> None

ACK backpressure engaged: drop the undrained backlog and gate every row, so the client resumes only at the IDR that _set_backpressure_enabled requests when the gate lifts.

Source Code
def flush_for_gate(self) -> None:
    """ACK backpressure engaged: drop the undrained backlog and gate every
    row, so the client resumes only at the IDR that
    _set_backpressure_enabled requests when the gate lifts."""
    if self.backlog or self.live_rows:
        self.backlog.clear()
        self.backlog_bytes = 0
        self.live_rows.clear()
paramself

Returns

None
func_want_sync(self) -> bool

Rate-limit this relay's keyframe (re)requests to the sync floor.

Source Code
def _want_sync(self) -> bool:
    """Rate-limit this relay's keyframe (re)requests to the sync floor."""
    now = time.monotonic()
    if now >= self._next_sync_req:
        self._next_sync_req = now + VIDEO_RELAY_SYNC_FLOOR_SECONDS
        return True
    return False
paramself

Returns

bool
funcoffer(self, item) -> bool

Accept, drop, or gate one encoded chunk.

Runs on the event loop and never awaits.

Source Code
def offer(self, item: dict) -> bool:
    """Accept, drop, or gate one encoded chunk.

    Runs on the event loop and never awaits.

    Args:
        item: The fan-out item (`data` memoryview, `owner` frame,
            `frame_id`).

    Returns:
        True when the caller should request a keyframe (data was dropped
        that only a sync point recovers).
    """
    data = item['data']
    size = len(data)
    is_h264 = size >= 10 and data[0] == 0x04
    is_idr = is_h264 and data[1] == 0x01
    dropped = False
    if (not is_idr and self.backlog
            and self.backlog_bytes + size > self.budget):
        self.backlog.clear()
        self.backlog_bytes = 0
        self.live_rows.clear()
        dropped = True
    deliver = True
    if is_h264:
        row = (data[4] << 8) | data[5]
        if is_idr:
            self.live_rows.add(row)
        elif row not in self.live_rows:
            deliver = False
            dropped = True
    if deliver:
        self.backlog.append(item)
        self.backlog_bytes += size
        self._wake.set()
    return dropped and self._want_sync()
paramself
paramitemdict

The fan-out item (data memoryview, owner frame, frame_id).

Returns

bool

True when the caller should request a keyframe (data was dropped

func_run(self) -> None

Drain the backlog onto the socket until stopped or the socket dies.

Source Code
async def _run(self) -> None:
    """Drain the backlog onto the socket until stopped or the socket dies."""
    try:
        while True:
            if self.stopped:
                return
            if not self.backlog:
                self._wake.clear()
                await self._wake.wait()
                continue
            item = self.backlog.popleft()
            data = item['data']
            self.backlog_bytes -= len(data)
            # Stamped before the await, and only for the display's
            # registered client: that is what the ACK RTT math measures.
            ds = self.server.display_clients.get(self.display_id)
            if ds is not None and ds.get('ws') is self.ws:
                fid = item['frame_id']
                ds['sent_timestamps'][fid] = time.monotonic()
                ds['last_sent_frame_id'] = fid
                ds['has_sent_any_frame'] = True
                if len(ds['sent_timestamps']) > SENT_FRAME_TIMESTAMP_HISTORY_SIZE:
                    ds['sent_timestamps'].popitem(last=False)
            try:
                await asyncio.wait_for(
                    self.ws.send_bytes(data),
                    timeout=SHARED_STREAM_SEND_TIMEOUT_SECONDS,
                )
            except asyncio.TimeoutError:
                # Checked before OSError: on 3.11+ TimeoutError subclasses it.
                data_logger.warning(
                    f"Video relay for '{self.display_id}' send stalled past "
                    f"{SHARED_STREAM_SEND_TIMEOUT_SECONDS}s; dropping client.")
                self.server.clients.discard(self.ws)
                _close_abandoned_ws(self.ws)
                return
            except (ConnectionResetError, OSError, RuntimeError):
                self.server.clients.discard(self.ws)
                return
            self.server._bytes_sent_in_interval += len(data)
    finally:
        group = self.server.video_relay_groups.get(self.display_id)
        if group is not None and group.get(self.ws) is self:
            del group[self.ws]
paramself

Returns

None

On this page

Edit on GitHub