livepeer / livepeer/livepeer-python-gateway

Encapsulate live-runner session payments in a stateful session/client object

Offen
#30 2 Kommentare 0 Reaktionen 1 zugewiesene Person Auf GitHub ansehen

@rickstaa arbeitet bereits daran.

Seit 17.6.2026.

  • #31 von @rickstaa — ohne Merge geschlossen
enhancement
Vorherrschende Sprache
Python
Sterne
1
Forks
7
PR-Merge-Kennzahlen
Keine gemergten PRs in 30 T.

Beschreibung

## Problem

Streamed live-runner sessions (trickle / websocket) must keep paying for as long as the session is open, because the orchestrator meters the open session by wall-clock/segments and drops it when the balance runs dry. Today the SDK exposes this as a free function, `run_session_payments(session, interval=...)`, which the caller must drive manually:

```python
payment_task = asyncio.create_task(run_session_payments(session, interval=...))
...
finally:
payment_task.cancel()
await payment_task
```

`reserve_session` already mints and attaches the `payment_session` credential to the returned `LiveRunnerSession`, but `LiveRunnerSession` is a frozen dataclass with no lifecycle, so it cannot own the background task. That pushes task creation and (critically) cancellation onto every consumer. Forget the `finally` and you silently stop paying or leak a task. The echo example (`example-apps/echo/client.py`) shows the boilerplate this requires.

## Prior art (already in our codebase)

Two better-encapsulated patterns already exist and prove the approach:

- `lv2v.py`: `Lv2vJob.start_payment_sender()` runs a per-output-segment payment sender, auto-started by `start_lv2v(start_payments=True)` and cancelled by `job.aclose()`. The consumer cannot forget it.
- daydreamlive/scope `ja/runner` (`server/livepeer_client.py`): `LivepeerClient` starts an interval `_payment_loop` right after connect, gated on `payment_session is not None`, and cancels it on teardown. This is effectively `run_session_payments` hand-rolled inside a client object.

## Proposal

Make `reserve_session` return a stateful session/client that owns the payment lifecycle, so payments are automatic for on-chain sessions:

```python
async with await reserve_session(app=..., signer_url=signer) as session:
# publish/consume trickle; payments start and stop automatically
```

- Auto-start the payment loop when `payment_session is not None` (on-chain only; no-op offchain).
- Auto-cancel on context exit / `aclose()`, alongside `stop_runner_session`.
- Keep `run_session_payments` as the internal primitive.
- This mirrors `start_lv2v(start_payments=True)` + `job.aclose()`.

Payments still need a scope anchor (the session must know when the stream ended); the context manager / `aclose()` provides it. The object cannot infer that media stopped on its own.

## Open questions

1. One unified `LiveRunnerSession` object that subsumes `Lv2vJob`, or keep lv2v separate and only wrap the general live-runner path? The former removes the parallel implementations; the latter is the minimal change.
2. Cadence semantics to standardize on: lv2v pays per output segment (only while media flows); `run_session_payments` pays on a fixed interval (pays even when paused-but-open). These differ in over-payment behavior and should be decided before standardizing the interface.

## Context

Follow-up from the echo example migration and the `run_session_payments` work (branch `rs/live-runner-session-payments`). Related: gateway PR #25 (SSE streaming).

Beitragsleitfaden

Für dieses Repository ist kein Beitragsleitfaden indexiert

Erste Schritte

  1. Lies das ganze Issue und danach den Beitragsleitfaden des Projekts.
  2. Schreib ins Issue, dass du es übernimmst — das erspart doppelte Arbeit.
  3. Forke das Repository und arbeite in einem Branch.
  4. Öffne einen Pull Request, der die Issue-Nummer nennt.

Bewertung

Dieses Issue wurde noch nicht bewertet.

Neue Issues direkt in Ihr Postfach

Eine kurze Übersicht über anfängerfreundliche GitHub-Issues.