breadcast/EVENTS.md
Breadway 17abeed7ae Fix Cast teardown leaks, keyframe latch, and DLNA session lifecycle
Dropped frames never requested a keyframe, SessionEnded skipped ordered
stop (portal/TV/FFI leak, next start could abort), and a late end could
kill the following cast. Failed starts left PlatformClientPosix alive.
DLNA leaked its HTTP server and ignored portal EOS.

Also: start no longer blocks the daemon actor, IPC accept/request loops
stay up, HLS Range is clamped, LAN IP follows the renderer subnet, and
the picker closes before the portal dialog and handles Escape.
2026-08-16 14:16:23 +08:00

44 lines
3.6 KiB
Markdown

# breadcast — bread event integration
breadcast is a standalone screen-mirroring app: it works exactly the same
with or without `breadd` running. When breadd *is* present, `breadcastd`
publishes events into the shared bread automation fabric and listens for a
small set of commands. See the parent `bread` repo's `Documentation.md`
specifically its "Namespaces" and "Integrating a bread\* app" sections — for
the general convention this follows.
App id: **`cast`**. Transport: `bread-utils`'s `bread_client` module
(feature `bread-client`) — `breadcastd` links it directly, since it's a
long-running process for both the command-subscription half and the
discovery-driven emit half.
## Events published (`bread.cast.*`)
| Event | Data | When |
|-------|------|------|
| `bread.cast.device_found` | `{ "id": "<mdns id or DLNA description URL>", "name": "<friendly name>", "model": "<model>", "protocol": "cast" \| "dlna" }` | A Chromecast/Google TV (mDNS) or DLNA/UPnP media renderer (SSDP) is discovered, or an already-known device changes (new host, new name). Re-resolutions that carry the same payload are **not** re-emitted — treat it as an upsert keyed by `id`, not an append-only log. `id` is protocol-specific and only unique *within* a protocol — a Cast device's mDNS id and a DLNA device's description URL share no namespace. |
| `bread.cast.mirroring_started` | `{ "device_id": "<id>", "device_name": "<name>", "protocol": "cast" \| "dlna" }` | A mirroring session successfully started, whether triggered by `bread.command.cast.start`, the `breadcast` GTK popup, or (once wired) any other IPC client. |
| `bread.cast.mirroring_stopped` | `{}` | A mirroring session ended, whether via an explicit stop (command, IPC, or GTK popup) or unprompted (the portal picker's "stop sharing", the receiver dropping the connection, a DLNA renderer stopping playback from its own remote). There is no separate "stopped by whom" distinction in this event — `breadcastd`'s own logs have that detail if needed. |
| `bread.cast.mirroring_failed` | `{ "device_id": "<id>", "error": "<message>" }` | A `start_cast`/`bread.command.cast.start` attempt failed before a session was established (device unreachable, portal capture denied, negotiation timeout, renderer rejected the stream, etc). |
## Commands honored (`bread.command.cast.*`)
| Command | Data | Effect |
|---------|------|--------|
| `bread.command.cast.start` | `{ "device_id": "<id>" }` | Starts mirroring to the given device (looked up across both the Cast and DLNA device lists — same `device_id` a `list_devices`/`device_list_changed` payload reports). Fire-and-forget: the outcome shows up as `bread.cast.mirroring_started`/`.failed`, not a reply to this command. No-ops (logged) if already casting. |
| `bread.command.cast.stop` | none | Stops the active mirroring session, if any. No-ops if already idle. |
Both commands are handled identically regardless of whether the session was
started from here, the `breadcast` GTK popup, or the IPC socket directly —
`breadcastd`'s daemon actor has exactly one notion of "the active session"
(see `breadcastd/src/daemon.rs`), not one per control surface.
## Fail-safe behavior
- If breadd isn't installed or isn't running, `emit` is a silent no-op
(`BreadClient::emit` never blocks or errors the caller) and the
command subscription simply never receives anything — breadcastd's actual
discovery functionality is entirely unaffected either way.
- If breadd restarts, the command subscription reconnects automatically
(`BreadClient::subscribe`'s background thread has its own backoff loop);
no restart of breadcastd is needed.