Skip to content

Relay: read further ahead, stream in large pieces, keep the Mac awake - #15

Merged
agkloop merged 1 commit into
mainfrom
perf/smooth-relay
Sep 25, 2026
Merged

agkloop merged 1 commit into
mainfrom
perf/smooth-relay

Conversation

@agkloop

@agkloop agkloop commented Sep 25, 2026

Copy link
Copy Markdown
Owner

Less buffering when the TV plays through the relay or plays a file from xcast <file>. Direct casts are unchanged: there the TV fetches from the site itself.

Changes

  1. Deeper HLS read-ahead (host/prefetch.go)
    • Fetches the next 4 segments of each playlist, 2 at a time. Before, it fetched only the next one.
    • Each playlist is tracked separately, so a stream with separate audio keeps both read-aheads.
    • A seek or quality switch drops, and cancels, what was fetched for the old position.
    • A failed segment is not fetched again and again; the TV's own request gets the real answer.
    • Cap is now 64 MB total, 16 MB per segment. Fetches still in flight count against it.
  2. Read-ahead for streamed bodies (host/ahead.go, new)
    • Covers a whole video file, or a segment that was not prefetched. The relay reads up to 16 MB past what the TV has taken, so a slow moment at the site is covered.
    • At most 4 responses have read-ahead at once. The rest are copied as before.
    • When the TV is waiting, it gets bytes at once. When it is busy, reads fill 128 KB chunks.
    • When the TV pauses, the buffer fills and reading stops, as before.
  3. Large copies: 128 KB pieces instead of 4 KB. The 4 KB came from bufio.Reader.WriteTo falling back to its own buffer size.
  4. Keep the Mac awake (host/awake.go, new)
    • While a relayed cast or a command-line file plays, and up to 30 minutes into a pause, the helper runs /usr/bin/caffeinate -i -w <pid>.
    • The display can still sleep. Closing a laptop's lid still puts the Mac to sleep.
    • If the helper dies, caffeinate exits by itself.

Sandbox change (install/xcast.sb)

New rules:

  • process-fork
  • process-exec of /usr/bin/caffeinate only
  • signal to its own children only
  • mach-lookup of com.apple.PowerManagement.control

caffeinate runs inside the same sandbox, and no other program can be started. The worst an attacker could do with this is keep the Mac from idle sleep. Documented in docs/SECURITY-MODEL.md.

With an older installed profile the exec fails, and casting works as before without keep-awake. Run ./install.sh to get the new profile.

Memory ceiling: 64 MB of prefetched segments plus 4 × 16 MB of body read-ahead.

Tests

  • New tests: TestPrefetchWindow, TestCopyAhead, TestKeepAwake, TestCaffeinate.
  • Each one fails when its fix is removed: depth set to 1, plain io.Copy, a 4 KB copy buffer, no hold, and no stale-timer guard.
  • go vet, staticcheck, govulncheck and go test -race are clean.
  • Checked by hand under the real profile:
    • caffeinate holds PreventUserIdleSystemSleep.
    • It is killed on release.
    • It exits by itself when the helper is killed.
    • /bin/ls is still refused.
    • -selftest passes.

Not tested: playback on a real TV. There are no before/after stall numbers yet.

Docs

  • README:
    • the comparison table row for computer sleep
    • the features list
    • the sandbox line
    • the sequence diagram note
    • a new troubleshooting row for buffering
    • the file map
  • docs/HOW-IT-WORKS.md and docs/SECURITY-MODEL.md.

🤖 Generated with Claude Code

Less buffering when the TV plays through the relay or from a local file.

- HLS read-ahead: the next four segments of each playlist, two fetched
  at once, instead of one. Playlists are tracked separately (demuxed
  audio has its own); a seek or quality switch drops and cancels what
  was fetched for the old position. A failed segment stays as a marker
  so it is not refetched in a loop. Cap raised to 64 MB (16 MB/item),
  with in-flight fetches reserved against it.
- Body read-ahead (ahead.go): any other streamed body (a whole video
  file, a segment that was not prefetched) is read up to 16 MB past
  what the TV has taken, so a slow moment at the site is covered. Four
  responses at a time; a starved TV gets bytes at once, a busy one
  lets reads fill 128 KB chunks. A paused TV fills the buffer and then
  back-pressures the site as before.
- Copies go in 128 KB pieces instead of the 4 KB the bufio.Reader's
  WriteTo fell back to.
- Keep awake (awake.go): while a relayed cast or a command-line file
  plays, and up to 30 minutes into a pause, the helper runs
  /usr/bin/caffeinate -i -w <pid>. The sandbox profile allows exactly
  that program, fork, signalling its own children, and the power
  management service; nothing else can be started. With an old profile
  the exec fails and casting works as before.

Tests: TestPrefetchWindow, TestCopyAhead, TestKeepAwake, TestCaffeinate;
each fails with its fix removed. Checked by hand under the real profile:
caffeinate holds PreventUserIdleSystemSleep, is killed on release, exits
by itself when the helper dies, /bin/ls is still refused, and -selftest
passes.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@agkloop
agkloop merged commit 17794e7 into main Sep 25, 2026
3 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant