Root cause (found by Opus 5 second-opinion review) of the freezing that survived every prior fix tonight: openscreen's Sender caps in-flight unacknowledged media at clamp(2*RTT, 66ms, 133ms) -- on a LAN that's pinned at the 66ms floor, about two frames at 30fps. When EnqueueFrame rejects a frame under that budget, facade.cc discarded the result (`(void)video_sender->EnqueueFrame(frame)`) and moved on. But vah264enc had already encoded the *next* frame as a P-slice depending on the one that just got silently dropped -- the encoder has no idea the drop happened, since it happens downstream of encoding, at this FFI boundary. The receiver sees an unbroken frame-ID sequence (nothing here told it otherwise) and decodes a P-slice against a reference picture it never received: a stuck/corrupted frame until the next regularly-scheduled key frame (up to ~2s, longer if that key frame is itself dropped the same way -- explains the 20+s outlier). Zero "receiver reported picture loss" lines across 195s of a visibly freezing session confirms the receiver genuinely never noticed anything was wrong, which a real decode-capability or packet-loss problem would have triggered. This also explains why moving to a faster network made it *worse*: the in-flight budget is RTT-derived, not bandwidth-derived, so more throughput doesn't raise the 66ms floor at all -- while 1080p tripled the per-frame packet count, increasing how often frames missed that window. Fixed the actual corruption: both drop paths in facade.cc (the EnqueueFrame rejection, and the pre-existing non-monotonic-capture-time guard) now set a `frame_chain_broken` flag, consumed once by breadcast_caststream_sender_needs_key_frame() so frame_pump_loop forces a key frame on the very next frame instead of chaining more P-slices onto a reference that no longer exists on the receiver. A separate flag from the existing `needs_key_frame` atomic because SchedulePoll's 100ms timer unconditionally overwrites that one with the Sender's own (unrelated) NeedsKeyFrame() reading, which would have silently clobbered this signal. Also reverted the Cast Streaming pipeline from tonight's 1080p experiment back to 1280x720 (build_video_pipeline_for_streaming + VideoParams::default(), which must agree -- a mismatch there is a separate protocol-level bug fixed earlier tonight). Not the root cause, but a real contributing factor per the packet-count reasoning above, and untangling it from the frame_chain_broken fix by changing both at once would make the next test ambiguous. Kept the 6000/1500 kbps bitrate range from earlier tonight, now actually paired with 720p for the first time. The deeper real fix -- raising openscreen's 66ms in-flight floor itself, which trades latency for headroom -- is out of scope for tonight; this targets the corruption mechanism (via the sanctioned, if awkward, needs_key_frame signal) without touching vendored openscreen constants. |
||
|---|---|---|
| .forgejo/workflows | ||
| breadcast | ||
| breadcast-caststream-sys | ||
| breadcast-core | ||
| breadcastd | ||
| ci | ||
| contrib | ||
| graphify-out | ||
| vendor/rust_cast-0.21.0 | ||
| .gitignore | ||
| AGENTS.md | ||
| bakery.toml | ||
| Cargo.lock | ||
| Cargo.toml | ||
| CONTRIBUTING.md | ||
| EVENTS.md | ||
| LICENSE | ||
| README.md | ||
breadcast
Cast your screen to a Chromecast, Google TV, or DLNA renderer on the LAN. Two binaries:
breadcastd— background daemon. Discovers Cast (mDNS) and DLNA/UPnP (SSDP) devices, owns the portal screen-capture / encode pipeline, and runs the live session: Cast Streaming (vendored openscreen) or DLNA/AVTransport. One active session at a time.breadcast— GTK4 Layer Shell popup. Thin IPC client ofbreadcastd: pick a device, start or stop a cast. Closing the popup does not interrupt an active session.
This is a bakery product. It is not shipped on the BOS ISO and is not part of the default desktop — install it yourself if you want it.
Install
bakery install breadcast
That puts breadcast and breadcastd on $PATH (usually ~/.local/bin),
installs contrib/breadcastd.service as a systemd user unit, enables it,
and starts the daemon. bakery doctor breadcast checks the system
packages listed in bakery.toml first.
Requirements
- A Wayland compositor with Layer Shell and
xdg-desktop-portalScreenCast (Hyprland is the primary target) - GTK 4.12+ and
gtk4-layer-shell - GStreamer plus
gst-plugin-pipewire,gst-plugins-bad,gst-plugin-va(vah264enc), andgst-plugin-hlssink3 jsoncppandopenssl(runtime deps of the vendored Cast Streaming code)
From source you also need a Rust toolchain (edition 2021).
Build from source
git clone https://git.breadway.dev/Breadway/breadcast
cd breadcast
cargo build --release
Binaries land at target/release/breadcast and target/release/breadcastd.
cp target/release/breadcast target/release/breadcastd ~/.local/bin/
cp contrib/breadcastd.service ~/.config/systemd/user/
systemctl --user daemon-reload
systemctl --user enable --now breadcastd
Usage
Start the daemon (or let the systemd unit handle it):
breadcastd
Open the device picker:
breadcast
Running breadcast again while it is open closes it (toggle). Click a
device to start mirroring — the portal picker asks which screen to share.
Stop mirroring ends the session. Status is Idle or Casting.
Discovery examples (optional, for debugging the LAN):
cargo run -p breadcast-core --example discover
cargo run -p breadcast-core --example dlna_discover
Hyprland keybind
On stock Hyprland, add the contents of contrib/hyprland.conf to your
hyprland.conf if you want Super+C and the frosted-glass blur:
layerrule = blur, breadcast
layerrule = ignorezero, breadcast
bind = $mainMod, C, exec, breadcast
BOS does not ship this app or a default keybind for it. Add one yourself if you install breadcast on a BOS machine.
bread event integration
breadcastd works the same with or without breadd. When breadd is
running, it publishes bread.cast.* and honors bread.command.cast.*.
See EVENTS.md for the bus contract. bread is not a bakery
dependency.
Theming
breadcast inherits its colour palette from bread-theme.