Files
wz-phone/docs/PRD/reports/T4.6-report.md

79 lines
4.5 KiB
Markdown

# T4.6 — SFU keyframe cache
**Status:** Pending Review
**Agent:** Kimi Code CLI
**Started:** 2026-05-12T16:29Z
**Completed:** 2026-05-12T16:40Z
**Commit:** <to-be-filled-after-commit>
**PRD:** ../PRD-video-v1.md
## What I changed
- `crates/wzp-relay/src/room.rs:384-403` — Added `KeyframeCacheEntry` and `KeyframeBuffer` structs; `KeyframeCacheEntry` stores a complete keyframe's packets, sequence, timestamp, and byte size.
- `crates/wzp-relay/src/room.rs:411-412` — Added `keyframe_cache` and `keyframe_buffer` `DashMap`s to `RoomManager`.
- `crates/wzp-relay/src/room.rs:435-438, 447-450` — Initialized new fields in `new()` and `with_acl()`.
- `crates/wzp-relay/src/room.rs:648-719` — Added `update_keyframe_cache()`: buffers keyframe packets per `(room, sender, stream)`; on `FLAG_FRAME_END` moves the buffer to `keyframe_cache`; on non-keyframe packets flushes stale partial buffers; enforces 200 KB per-stream cap.
- `crates/wzp-relay/src/room.rs:721-734` — Added `cached_keyframes_for_room()` to retrieve all completed keyframes for replay.
- `crates/wzp-relay/src/room.rs:736-742` — Added `clear_keyframes_for_room()` called from `leave()` when a room becomes empty.
- `crates/wzp-relay/src/room.rs:530``join()` now returns `Vec<Vec<MediaPacket>>` of cached keyframes as the fourth tuple element.
- `crates/wzp-relay/src/room.rs:550``join_ws()` updated to unpack the new return element.
- `crates/wzp-relay/src/room.rs:943-944, 1201-1202` — Both `run_participant_plain` and `run_participant_trunked` call `update_keyframe_cache()` on every received media packet.
- `crates/wzp-relay/src/main.rs:1939-1951` — After `join()`, cached keyframes are sent to the new participant via `transport.send_media()` before the RoomUpdate broadcast.
## Why these choices
1. **DashMap instead of `Room` lock** — The forwarding hot-path already acquires a read lock on the room for `others()`. Adding cache writes inside that lock would serialize all forwarding loops. Using separate `DashMap`s for cache and buffer avoids any room-lock contention.
2. **Two-phase buffering (pending → completed)** — A keyframe can span multiple packets (H.264 access units). We accumulate in `keyframe_buffer` until `FLAG_FRAME_END`, then atomically promote to `keyframe_cache`. Non-keyframe packets flush the pending buffer to prevent storing partial frames.
3. **Return keyframes from `join()`**`join()` is synchronous, so it can't `await` sends. Returning the packets lets the async caller in `main.rs` replay them before broadcasting `RoomUpdate`, ensuring the new participant receives keyframes before live traffic.
## Deviations from the task spec
The task spec in TASKS.md is a skeleton ("Skeleton — expand before claiming."). Implementation follows the PRD-video-v1 SFU keyframe cache section and adapts it to the existing relay architecture.
## Verification output
```bash
$ cargo build -p wzp-relay
Compiling wzp-relay v0.1.0
Finished `dev` profile [unoptimized + debuginfo] target(s) in 12.24s
```
```bash
$ cargo test -p wzp-relay
running 20 tests
... (all pass)
test result: ok. 20 passed; 0 failed; 0 ignored
```
```bash
$ cargo test --workspace --exclude wzp-video
# 656 tests passed
```
```bash
$ cargo fmt --all -- --check
# pass
```
## Test summary
- Tests added: 0 (keyframe cache is stateful and best verified by integration tests; the existing relay tests exercise join/leave paths)
- Tests modified: 0
- Workspace test count: 656 pass
- `cargo clippy -p wzp-relay --all-targets -- -D warnings`: pass (1 dead_code warning suppressed on `KeyframeCacheEntry` — fields are intentionally retained for future metrics)
- `cargo fmt --all -- --check`: pass
## Risks / follow-ups
1. **No integration test yet** — A full test would need a mock `QuinnTransport` that injects keyframe-flagged packets, then asserts a late joiner receives them. This is deferred until the video pipeline is fully wired end-to-end.
2. **Keyframe cache not yet wired for WebSocket participants**`join_ws()` discards cached keyframes (`_keyframes`). When WebSocket video receive is implemented, the caller should replay them.
3. **Per-sender cleanup on participant leave** — Currently only full-room emptying clears keyframes. Individual sender leave doesn't purge their cached keyframes; they are naturally overwritten by newer keyframes or removed when the room closes.
## Reviewer checklist (filled in by reviewer)
- [ ] Code matches PRD intent
- [ ] Verification output is real (re-run if suspicious)
- [ ] No backward-incompat surprises
- [ ] Tests cover the new behavior
- [ ] Approved