Skip to content

VoIP 1:1 video calls - #1024

Merged
jlucaso1 merged 13 commits into
mainfrom
feat/voip-video
Jul 16, 2026
Merged

jlucaso1 merged 13 commits into
mainfrom
feat/voip-video

Conversation

@jlucaso1

@jlucaso1 jlucaso1 commented Jul 13, 2026

Copy link
Copy Markdown
Collaborator

Summary

Adds interoperable 1:1 video calls to the existing VoIP implementation. Calls can start with video, upgrade from audio to video, return to audio, and accept the same transitions initiated by an official WhatsApp client.

The library remains codec-neutral: consumers provide and receive complete H.264 Annex-B access units through VideoSource / VideoSink. Encoding, decoding, capture, and display stay outside the core crates; the CLI example uses ffmpeg and ffplay without adding codec dependencies to the library.

This is additive to the existing 1:1 voice path. Bidirectional audio continues over its established pipeline while video is independently signaled, demultiplexed, encrypted, paced, and recovered.

WhatsApp interoperability

The implementation covers the media and signaling profile verified against WhatsApp Web/Android captures and live Android calls:

  • WhatsApp SSRC derivation, including the video participant slot and dynamic STUN StreamDescriptors for live audio/video SSRCs;
  • PT-97 RTP with the captured extension layout, marker/timestamp behavior, WARP authentication, E2E-SRTP, and answering-device rekeying;
  • directional/shared relay SRTCP derivation, native RTCP CNAME/SDES, and periodic Sender Reports at the advertised 1.5 s cadence using NTP wall time and actual sender/reception statistics;
  • authenticated peer SR/RR, compact reports, NACK, PLI, and FIR parsing, including WhatsApp's feedback profile bit;
  • per-sender SRTCP replay windows that reject duplicate/stale authenticated indices, accept bounded reordering and 31-bit wrap, and never commit state for forged packets;
  • offer, preaccept, accept, typed video acknowledgements, orientation, upgrade/downgrade, and the standalone Enabled transition used to complete accepted upgrades.

The Android peer reports reception of the Rust participant's video SSRC and renders outbound video from both synthetic input and a real V4L2 webcam.

Empirically confirmed webcam recovery

The webcam-only failure was isolated by replaying the exact captured webcam Annex-B stream and inspecting authenticated peer RTCP. The H.264 was valid and reached Android, but Android repeatedly sent PLI for the local video SSRC. Continuing to send dependent frames before the next IDR kept the official decoder outside a valid recovery point.

The engine now treats authenticated PLI/FIR as a decoder resynchronization boundary: dependent access units are withheld without consuming RTP/SRTP sequence state until the next IDR, then normal transmission resumes. The live V4L2 test changed from PLI every ~440 ms with no picture to one recovery request followed by continuous rendered webcam video.

The same invariant protects from-start video, reactivation, upgrade ungating, and relay backpressure. The recovery gate clears only after an IDR packetizes successfully. Disable purges queued, unstarted video access units while preserving any batch already on the transport, so reactivation resumes at a complete IDR without truncating an in-flight frame; audio and control traffic stay live.

API and lifecycle

let handle = client
    .voip()
    .call(peer)
    .audio(mic, speaker)
    .video(h264_source, h264_sink)
    .start()
    .await?;

handle.start_video(new_source, new_sink).await?;
handle.stop_video().await?;
  • VideoSource exposes its RTP timestamp stride, so 15 fps, 20 fps, 30 fps, and other valid 90 kHz cadences do not drift; zero stride is rejected at construction.
  • Upgrade video remains send-gated until peer acceptance. Downgrade drains stale source access units without resetting RTP/SRTP state.
  • Accepted peer upgrades become locally visible and ungate media only after the standalone Enabled send succeeds. A failed second handshake send emits phase-tagged diagnostics, tears down endpoints, and clears local video state in both initiator and acceptor paths, leaving the official peer to perform its normal incomplete-upgrade timeout.
  • Incoming video-state events preserve routed participant/recipient metadata in typed acknowledgements, bind transitions to the call generation, and hold serialization through every post-ack effect and accepted-upgrade Enabled send. Unacknowledged or stale same-ID transitions cannot mutate a replacement call, and a committed state supersedes an older queued event under backpressure.
  • Signaling state derives its wire markers from VideoState, preventing invalid marker/state combinations.
  • Failed setup before a peer-visible transition rolls back endpoints and explicitly terminates an already-preaccepted call. Final accept is sent only after camera/ffmpeg has produced a decodable SPS/PPS/IDR.
  • ffmpeg reader tasks are cancelled on readiness failure, timeout, source replacement, terminal call removal, and source drop. Video feeds retain source ownership for exactly the call lifetime.
  • Peer termination during media preparation is tombstoned so a dead call cannot finish startup later.
  • Video teardown hooks are generation-bound, consumed exactly once off-lock, and rearmed on every activation; stop/restart and all terminal paths release the correct endpoints without touching a same-call-id replacement.
  • Video state and cadence controls use a lossless FIFO, so a Disable purge always precedes a later Enable; orientation is isolated in a one-slot latest-value mailbox and cannot evict negotiated transitions.

H.264 media plane

  • Single-NAL and FU-A packetization; single-NAL, STAP-A, and FU-A reassembly.
  • SPS/PPS/IDR ordering compatible with the official receiver; encoder-only AUDs are omitted from RTP.
  • Timestamp-boundary recovery when markers are lost, sequence-aware fragment-loss handling, allocation caps, forged-tag rejection, and immediate ordered draining when one packet completes multiple access units.
  • Complete access units are the backpressure unit, so overload cannot leave half an IDR on the wire.
  • From-start video, upgrades/downgrades, receiver rekey, RTP/RTCP wrap, malformed input, authenticated feedback, replay rejection, and keyframe recovery have deterministic tests/KATs.

CLI example

Every call/listen mode accepts --video; v toggles video during a call and q performs a signaled hangup. Sources can be an OS webcam, a file/URL, or WA_VIDEO_INPUT=testsrc; sinks can be an ffplay window, raw .h264, or discard mode.

The example asynchronously classifies input paths, probes V4L2 capabilities, selects an exact supported capture mode, normalizes pixel format/geometry, and waits for a decodable SPS/PPS/IDR before accepting the call. Its default encoder contract matches the captured WhatsApp Web high-quality tier: H.264 Constrained Baseline Level 3.1, 1280×720 at 20 fps, about 1.98 Mbps, one slice per frame, repeated SPS/PPS, and a 60-frame GOP. WA_VIDEO_SIZE, WA_VIDEO_FPS, and WA_VIDEO_BITRATE_KBPS remain explicit overrides.

The preview uses arrival-time timestamps, bounded low-latency queues, and inverse device_orientation correction. This prevents long-run delay accumulation and correctly displays Android portrait video. Official clients still adapt quality dynamically; the example matches a captured high-quality tier but does not claim to implement WhatsApp's full bandwidth estimator.

Validation

  • cargo fmt --all -- --check
  • cargo clippy --all --tests -- -D warnings
  • cargo test --workspace --exclude e2e-tests
    • wacore: 1,403 passed, 1 ignored
    • whatsapp-rust: 1,068 passed, 1 ignored
    • whatsapp-rust-voip-cli: 16 passed
  • cargo test -p wacore --features voip voip::: 297 passed, 1 diagnostic ignored
  • cargo test -p whatsapp-rust --features voip voip::: 53 passed
  • cargo test -p whatsapp-rust --features voip handlers::call::tests::: 24 passed
  • cargo build -p whatsapp-rust-voip-cli --release
  • live Android interoperability: stable bidirectional audio, inbound video decode, outbound synthetic and V4L2 webcam video rendered on the phone, portrait correction on desktop, and sustained low-latency preview

cargo test --all additionally requires the documented E2E mock server; GitHub Actions supplies that environment.

Scope

  • 1:1 calls only; group video is not part of this PR.
  • H.264 Annex-B transport only; pixel formats and codec implementations remain consumer-owned.
  • VoIP feature scope only, with no new runtime codec dependency in wacore or whatsapp-rust.

@coderabbitai

coderabbitai Bot commented Jul 13, 2026

Copy link
Copy Markdown

Review Change Stack

📝 Walkthrough

Walkthrough

This PR adds end-to-end VoIP video support across signaling, H.264 RTP media, SRTP/SRTCP, engine and drive-loop control, public facade APIs, call registry coordination, and an ffmpeg/ffplay-backed CLI.

Changes

VoIP video plane

Layer / File(s) Summary
Video signaling contracts and dispatch
wacore/src/types/call.rs, wacore/src/stanza/call.rs, src/handlers/call.rs
Adds video call states, offer/accept/preaccept signaling, typed acknowledgements, orientation handling, and generation-aware event dispatch.
H.264, RTP, RTCP, and secure media transport
wacore/src/voip/h264.rs, wacore/src/voip/rtp.rs, wacore/src/voip/session.rs, wacore/src/voip/rtcp.rs, wacore/src/voip/e2e_srtp.rs, wacore/src/voip/hbh_srtp.rs, wacore/src/voip/stun.rs
Adds Annex-B packetization and reassembly, video RTP extensions, SRTCP protection and replay handling, RTCP reporting, video SSRC derivation, and dynamic media descriptors.
Engine and drive-loop video plane
wacore/src/voip/engine.rs, wacore/src/voip/driver.rs, wacore/src/voip/demux.rs, src/voip/transport.rs
Routes encoded video through the engine and drive loop, supports enable/disable and keyframe gating, demultiplexes PT-97, schedules RTCP reports, and preserves audio-only behavior.
Facade APIs and call-state wiring
src/voip/facade.rs, src/voip/video.rs, src/voip/mod.rs, wacore/src/voip/registry.rs
Adds public video endpoint traits, call builders, upgrade/accept/stop methods, per-call video channels, teardown hooks, and generation-guarded state updates.
CLI video pipeline and controls
examples/voip-cli/src/main.rs, examples/voip-cli/src/video.rs
Adds --video, environment-based ffmpeg/ffplay configuration, subprocess video source and sink handling, live-call v/q controls, and video-aware call orchestration.

Estimated code review effort: 5 (Critical) | ~120 minutes

Possibly related PRs

Suggested labels: api-design

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
Title check ✅ Passed The title clearly summarizes the main change: adding 1:1 video calls to VoIP.
Description check ✅ Passed The description matches the changeset and describes the new 1:1 video call support.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/voip-video

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@greptile-apps

greptile-apps Bot commented Jul 13, 2026

Copy link
Copy Markdown

Greptile Summary

This PR adds interoperable 1:1 video calling to the existing VoIP stack. The main changes are:

  • H.264 packetization, reassembly, pacing, encryption, and recovery.
  • Video offer, acceptance, upgrade, downgrade, and orientation signaling.
  • Authenticated RTCP feedback, replay protection, and sender reports.
  • Public video source, sink, and call-control APIs.
  • An ffmpeg-based CLI for capture, playback, and live video controls.

Confidence Score: 5/5

This looks safe to merge.

  • Failed video handshakes now clean up local state.
  • Video transitions remain generation-bound across asynchronous sends.
  • State controls are preserved while orientation updates are coalesced.
  • H.264 reassembly rejects stale and incomplete packet sequences.
  • No blocking issues were found in the updated code.

Important Files Changed

Filename Overview
src/handlers/call.rs Adds serialized video-state acknowledgements, generation checks, event delivery, and failed-handshake cleanup.
src/voip/facade.rs Adds public video lifecycle APIs, endpoint ownership, signaling transitions, and rollback behavior.
wacore/src/voip/h264.rs Adds H.264 packetization and sequence- and timestamp-aware access-unit reassembly.
wacore/src/voip/driver.rs Adds reliable video-state controls and bounded, coalesced orientation updates.
examples/voip-cli/src/video.rs Adds ffmpeg and ffplay video sources, sinks, camera probing, readiness checks, and low-latency queues.

Sequence Diagram

%%{init: {'theme': 'neutral'}}%%
sequenceDiagram
    participant App
    participant Facade
    participant Signaling
    participant Registry
    participant Engine
    participant Peer

    App->>Facade: Start or accept video
    Facade->>Registry: Attach endpoints and teardown hook
    Facade->>Signaling: Send video transition
    Signaling->>Peer: Upgrade request or acceptance
    Peer-->>Signaling: Typed acknowledgement
    Signaling->>Peer: Enabled
    alt Send succeeds and call generation is current
        Signaling->>Registry: Commit video state
        Registry->>Engine: Enable video
        Signaling-->>App: VideoStateChanged
    else Send fails or call is stale
        Signaling->>Registry: Run generation-bound teardown
        Registry->>Engine: Disable video
    end
Loading
%%{init: {'theme': 'base', 'themeVariables': {"darkMode": true, "background": "#0d1117", "primaryColor": "#21262d", "primaryTextColor": "#e6edf3", "primaryBorderColor": "#8b949e", "lineColor": "#8b949e", "textColor": "#e6edf3", "edgeLabelBackground": "#161b22", "actorBkg": "#21262d", "actorBorder": "#8b949e", "actorTextColor": "#e6edf3", "actorLineColor": "#8b949e", "signalColor": "#8b949e", "signalTextColor": "#e6edf3", "noteBkgColor": "#373320", "noteBorderColor": "#d4a72c", "noteTextColor": "#f0e6c0", "labelBoxBkgColor": "#21262d", "labelBoxBorderColor": "#8b949e", "labelTextColor": "#e6edf3", "loopTextColor": "#e6edf3", "activationBkgColor": "#30363d", "activationBorderColor": "#8b949e"}}}%%
sequenceDiagram
    participant App
    participant Facade
    participant Signaling
    participant Registry
    participant Engine
    participant Peer

    App->>Facade: Start or accept video
    Facade->>Registry: Attach endpoints and teardown hook
    Facade->>Signaling: Send video transition
    Signaling->>Peer: Upgrade request or acceptance
    Peer-->>Signaling: Typed acknowledgement
    Signaling->>Peer: Enabled
    alt Send succeeds and call generation is current
        Signaling->>Registry: Commit video state
        Registry->>Engine: Enable video
        Signaling-->>App: VideoStateChanged
    else Send fails or call is stale
        Signaling->>Registry: Run generation-bound teardown
        Registry->>Engine: Disable video
    end
Loading

Reviews (19): Last reviewed commit: "fix(voip): preserve video control transi..." | Re-trigger Greptile

Comment thread src/voip/facade.rs Outdated
Comment thread src/voip/facade.rs
Comment thread src/handlers/call.rs Outdated
Comment thread wacore/src/voip/h264.rs

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 5d821709ff

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread src/voip/facade.rs Outdated
Comment thread src/handlers/call.rs Outdated
@github-actions

github-actions Bot commented Jul 13, 2026

Copy link
Copy Markdown

📦 Binary size report

Metric main PR Δ
bin size (stripped) 10.90 MiB 10.90 MiB +2.44 KiB (+0.02%) 🔺
bin .text 8.89 MiB 8.89 MiB +2.38 KiB (+0.03%) 🔺
bin allocated (text+data+bss) 10.90 MiB 10.90 MiB 0
llvm-lines wacore 508,533 510,193 +1,660 (+0.33%) 🔺
llvm-lines wacore copies 17,438 17,453 +15 (+0.09%) 🔺
llvm-lines whatsapp-rust lib 776,627 776,652 +25 (+0.00%) 🔺
llvm-lines whatsapp-rust lib copies 25,235 25,235 0
deps crates (Cargo.lock) 472 472 0
.text per crate
Crate main PR Δ
.text whatsapp_rust 1.68 MiB 1.68 MiB +2.23 KiB (+0.13%) 🔺
.text wacore 529.43 KiB 529.50 KiB +66 B (+0.01%) 🔺
.text wacore_binary 148.45 KiB 148.45 KiB 0
.text wacore_libsignal 195.25 KiB 195.25 KiB 0
.text wacore_appstate 158.25 KiB 158.25 KiB 0
.text wacore_noise 26.03 KiB 26.03 KiB 0
.text waproto 1.60 MiB 1.60 MiB 0
.text whatsapp_rust_sqlite_storage 513.08 KiB 513.08 KiB 0
.text whatsapp_rust_tokio_transport 43.69 KiB 43.69 KiB 0
.text whatsapp_rust_ureq_http_client 10.47 KiB 10.47 KiB 0
.text std 1.01 MiB 1.01 MiB +51 B (+0.00%) 🔺
.text other deps 2.95 MiB 2.95 MiB 0
Top movers (cargo-bloat attribution)
Crate main PR Δ
whatsapp_rust 1.68 MiB 1.68 MiB +2.23 KiB (+0.13%)

Baseline: 2851ca338 (latest main run) · Head: e88fd57c5 · Graphs

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@examples/voip-cli/src/video.rs`:
- Around line 77-91: Convert ensure_tool to an async function using
tokio::process::Command and await its status probe instead of blocking with
std::process::Command. Propagate async and await through spawn_video_source and
spawn_video_sink and their async callers, including run_video_loopback and the
relevant main.rs paths. In spawn_video_sink, perform the tool probe once, reuse
its result for both the warning and use_window decision, and preserve the
existing error handling and behavior.

In `@wacore/src/stanza/call.rs`:
- Around line 707-738: Add a stanza id to the video-state call path by extending
VideoStateParams with an id value or generating one before build_video_state,
then apply it to the enclosing call wrapper produced by build_video_state.
Ensure every video upgrade and downgrade sent through send_node carries the id
needed for typed video-ack correlation.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository UI (base), Organization UI (inherited)

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: 284846bf-68b4-40a5-ac7a-11f506ba1182

📥 Commits

Reviewing files that changed from the base of the PR and between d9e693f and 5d82170.

📒 Files selected for processing (18)
  • examples/voip-cli/src/main.rs
  • examples/voip-cli/src/video.rs
  • src/handlers/call.rs
  • src/voip/facade.rs
  • src/voip/mod.rs
  • src/voip/transport.rs
  • src/voip/video.rs
  • wacore/benches/voip_benchmark.rs
  • wacore/src/stanza/call.rs
  • wacore/src/types/call.rs
  • wacore/src/voip/driver.rs
  • wacore/src/voip/engine.rs
  • wacore/src/voip/h264.rs
  • wacore/src/voip/mod.rs
  • wacore/src/voip/registry.rs
  • wacore/src/voip/rtp.rs
  • wacore/src/voip/session.rs
  • wacore/src/voip/ssrc.rs

Comment thread examples/voip-cli/src/video.rs Outdated
Comment thread wacore/src/stanza/call.rs

@cubic-dev-ai cubic-dev-ai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

All reported issues were addressed across 18 files

Confidence score: 5/5

  • Safe to merge after the addressed issues were fixed.

You’re at about 90% of the monthly reviewed-line limit. You may want to disable incremental reviews to conserve quota. Reviews will continue until that limit is exceeded. If you need help avoiding interruptions, please contact contact@cubic.dev.

Tip: cubic can generate docs of your entire codebase and keep them up to date. Try it here.

Re-trigger cubic

Comment thread wacore/src/voip/rtp.rs
Comment thread wacore/src/voip/ssrc.rs Outdated
Comment thread wacore/src/voip/driver.rs
Comment thread wacore/src/voip/h264.rs
Comment thread wacore/src/stanza/call.rs Outdated
Comment thread examples/voip-cli/src/video.rs
Comment thread wacore/src/types/call.rs
Comment thread wacore/src/voip/registry.rs
Comment thread examples/voip-cli/src/main.rs Outdated
Comment thread examples/voip-cli/src/main.rs
@jlucaso1

Copy link
Copy Markdown
Collaborator Author

@codex review

Comment thread src/voip/facade.rs Outdated
Comment thread wacore/src/voip/h264.rs Outdated

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@examples/voip-cli/src/video.rs`:
- Around line 61-66: Update the WA_VIDEO_INPUT parsing match so existing paths
are classified as VideoInput::Media only when they are not Linux device nodes;
allow values such as /dev/video0 to fall through to the VideoInput::Webcam arm
while preserving URL and regular media-path handling.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository UI (base), Organization UI (inherited)

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: 5864883f-07fe-4502-b5fb-966db4f6f74e

📥 Commits

Reviewing files that changed from the base of the PR and between 5d82170 and d605f22.

📒 Files selected for processing (18)
  • examples/voip-cli/src/main.rs
  • examples/voip-cli/src/video.rs
  • src/handlers/call.rs
  • src/voip/facade.rs
  • src/voip/mod.rs
  • src/voip/transport.rs
  • src/voip/video.rs
  • wacore/benches/voip_benchmark.rs
  • wacore/src/stanza/call.rs
  • wacore/src/types/call.rs
  • wacore/src/voip/driver.rs
  • wacore/src/voip/engine.rs
  • wacore/src/voip/h264.rs
  • wacore/src/voip/mod.rs
  • wacore/src/voip/registry.rs
  • wacore/src/voip/rtp.rs
  • wacore/src/voip/session.rs
  • wacore/src/voip/ssrc.rs

Comment thread examples/voip-cli/src/video.rs

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 39e906b2e0

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread wacore/src/voip/driver.rs Outdated
Comment thread examples/voip-cli/src/main.rs Outdated
Comment thread src/voip/facade.rs Outdated
Comment thread src/handlers/call.rs Outdated
Comment thread wacore/src/voip/h264.rs Outdated

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@src/handlers/call.rs`:
- Around line 177-182: Update the cancellation handling around
build_call_video_ack in the call handler so *cancelled is set to true only after
a typed video ack is successfully built. If the builder returns None, leave
cancellation unchanged so the router can send its generic acknowledgement;
preserve the existing send_node error warning for successfully built typed
acknowledgements.

In `@wacore/src/voip/registry.rs`:
- Around line 201-209: Update run_video_teardown to avoid invoking the
video_teardown callback while holding inner: store the hook as an Arc-compatible
value, clone it while the registry lock is held, release the lock, then invoke
the cloned callback. Preserve the existing no-op behavior when the call ID or
hook is absent and align with the lock-release pattern used by the other
dispatch helpers.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository UI (base), Organization UI (inherited)

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: f6eb3eb4-d049-47d9-8836-25deddb0ad8b

📥 Commits

Reviewing files that changed from the base of the PR and between 39e906b and 865d3f2.

📒 Files selected for processing (18)
  • examples/voip-cli/src/main.rs
  • examples/voip-cli/src/video.rs
  • src/handlers/call.rs
  • src/voip/facade.rs
  • src/voip/mod.rs
  • src/voip/transport.rs
  • src/voip/video.rs
  • wacore/benches/voip_benchmark.rs
  • wacore/src/stanza/call.rs
  • wacore/src/types/call.rs
  • wacore/src/voip/driver.rs
  • wacore/src/voip/engine.rs
  • wacore/src/voip/h264.rs
  • wacore/src/voip/mod.rs
  • wacore/src/voip/registry.rs
  • wacore/src/voip/rtp.rs
  • wacore/src/voip/session.rs
  • wacore/src/voip/ssrc.rs

Comment thread src/handlers/call.rs Outdated
Comment thread wacore/src/voip/registry.rs Outdated

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

♻️ Duplicate comments (1)
src/handlers/call.rs (1)

177-196: 🩺 Stability & Availability | 🟠 Major | ⚡ Quick win

We're still killing the generic ack before we know we can send a typed one — that's a broken call, and I flagged this last time.

*cancelled = true runs at Line 177 unconditionally. When build_call_video_ack returns None (empty wrapper stanza id) we hit the None branch, send no typed ack, yet the generic router ack is already suppressed. Net result: the peer gets zero acknowledgement, times out (~5s), and the upgrade reverts. That's exactly the failure mode we don't want to ship. Move the cancellation so it only fires once a typed ack is actually built; otherwise let the generic ack stand.

🐛 Gate the suppression on a real typed ack
-                        *cancelled = true;
                         // The typed ack is what tells the peer we received its state; only COMMIT
                         // the local plane transition once it is sent. ...
                         let acked = match build_call_video_ack(&call) {
-                            Some(ack) => match client.send_node(ack).await {
+                            Some(ack) => {
+                                // Only suppress the generic ack once we own the typed one.
+                                *cancelled = true;
+                                match client.send_node(ack).await {
                                     Ok(()) => true,
                                     Err(e) => {
                                         warn!("call: failed to send typed video ack: {e}");
                                         false
                                     }
-                            },
+                                }
+                            }
                             None => {
                                 warn!("call: video stanza has no id; cannot send the typed ack");
                                 false
                             }
                         };
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/handlers/call.rs` around lines 177 - 196, Move the unconditional
*cancelled = true assignment out of the start of the video-ack flow and gate it
on build_call_video_ack returning Some. Preserve the existing typed-ack send
behavior, but leave the generic router acknowledgement unsuppressed when no
typed ack can be built, including the missing-stanza-id case.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@wacore/src/voip/driver.rs`:
- Around line 297-317: Update the video_ctl handling in the main select loop so
it invokes the same inline poll_timeout()/Input::Timeout processing used by the
relay, mic, and video_in arms after dispatching a control message. Preserve the
existing VideoControl behavior while ensuring a continuously ready video_ctl
channel cannot defer overdue keepalive handling.

---

Duplicate comments:
In `@src/handlers/call.rs`:
- Around line 177-196: Move the unconditional *cancelled = true assignment out
of the start of the video-ack flow and gate it on build_call_video_ack returning
Some. Preserve the existing typed-ack send behavior, but leave the generic
router acknowledgement unsuppressed when no typed ack can be built, including
the missing-stanza-id case.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository UI (base), Organization UI (inherited)

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: e6f272cd-4f3d-4033-8930-f654c0f94535

📥 Commits

Reviewing files that changed from the base of the PR and between 865d3f2 and 9aca2cc.

📒 Files selected for processing (18)
  • examples/voip-cli/src/main.rs
  • examples/voip-cli/src/video.rs
  • src/handlers/call.rs
  • src/voip/facade.rs
  • src/voip/mod.rs
  • src/voip/transport.rs
  • src/voip/video.rs
  • wacore/benches/voip_benchmark.rs
  • wacore/src/stanza/call.rs
  • wacore/src/types/call.rs
  • wacore/src/voip/driver.rs
  • wacore/src/voip/engine.rs
  • wacore/src/voip/h264.rs
  • wacore/src/voip/mod.rs
  • wacore/src/voip/registry.rs
  • wacore/src/voip/rtp.rs
  • wacore/src/voip/session.rs
  • wacore/src/voip/ssrc.rs

Comment thread wacore/src/voip/driver.rs Outdated
…peg example

The video media plane rides the audio call's relay and E2E keys: H.264 Annex-B access units are RFC 6184-packetized (single NAL / FU-A), protected by the same per-participant E2E-SRTP + WARP MI tag under a video SSRC (slot 2), and demuxed from audio by payload type (97). The library never touches pixels: VideoSource/VideoSink carry pre-encoded AUs, so the codec stays with the consumer.

Signaling implements the in-call <video state=N> handshake (UpgradeRequestV2 -> UpgradeAccept -> Enabled; Stopped/Disabled to downgrade), with the upgrade marker attr only on the request, a generated wrapper id so the peer's typed <ack class="call" type="video"> can correlate, and that typed ack replacing the generic one (an untyped ack makes the requester revert the upgrade). CallHandle gains start_video/accept_video/stop_video; the builders gain .video(source, sink) for video-from-the-start calls; peer states surface as CallEvent::VideoStateChanged.

A downgrade preserves the video SRTP send sequence/ROC (the pipeline is deactivated, not dropped) so a re-upgrade never resets the packet index under the same key+SSRC and repeats the AES-CTR keystream. FU-A reassembly tracks the RTP sequence number and drops a partial NAL on a gap rather than emitting it truncated. Local video setup rolls back on a failed signaling send, a peer reject/cancel releases the source, and handle ops are generation-guarded against glare.

The voip-cli example gains --video on every subcommand, using ffmpeg/ffplay subprocesses as the external codec (webcam per OS, any file/URL, or a testsrc pattern; window or .h264-file sink), an IDR-gated backpressure dropper on both sides, and a stdin toggle (v/q) for mid-call upgrade/downgrade.

Wire details taken from the meowcaller/WaCalls reference are marked as live-validation points (video SSRC slot word, PT 97, no SFrame on video, <video> node shape in offer/accept, the fixed 15 fps timestamp stride); the signaling flow matches what the mock server proved against real WA Web clients. No new dependencies; everything stays inside the existing voip feature.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 4fae9659c6

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread src/handlers/call.rs Outdated
Comment thread src/voip/facade.rs
Comment thread wacore/src/voip/engine.rs

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 9

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@examples/voip-cli/src/main.rs`:
- Around line 740-759: Replace production Mutex::lock().unwrap() calls
throughout the affected CallState handlers, including begin_call_startup,
complete_call_startup, record_peer_terminate, and the additional referenced
sections, with a shared poison-recovering lock helper or a non-poisoning mutex.
Preserve each handler’s existing state-update behavior while ensuring poisoned
locks do not panic during event handling, cleanup, or UI tasks.
- Around line 102-108: Update video_source_is_ignored and its callers so the
WA_VIDEO_INPUT warning is emitted only for audio loopback and rejecting listen
mode; do not warn for listen accept or call modes because their stdin UI can use
the variable for upgrades. Adjust the associated tests to cover these
mode-specific behaviors.
- Around line 817-833: Rework the call setup flow around respond_to_offer and
start_media so it sends a preaccept, prepares camera/sink/ffmpeg endpoints,
waits for the first IDR, and only then sends the final accept. Apply the same
ordering to the related call paths at the other indicated locations, and
explicitly terminate the call if media preparation or readiness fails after
setup begins.

In `@examples/voip-cli/src/video.rs`:
- Around line 197-210: The VideoOpts::from_env method performs a blocking
Path::exists check on the async setup path. Move path classification out of this
synchronous parser by making the setup flow use Tokio filesystem APIs or by
separating pure environment parsing from asynchronous path existence checking,
while preserving the existing VideoInput selection behavior.
- Around line 565-712: Update spawn_video_source to retain a JoinHandle or abort
handle for the spawned ffmpeg-reading task. Abort the task before returning on
every pre-IDR timeout/error path, and ensure dropping FfmpegVideoSource also
cancels the task; preserve normal startup and frame delivery behavior.

In `@wacore/src/stanza/call.rs`:
- Around line 784-805: Update build_video_state and VideoStateParams to remove
the upgrade_marker Boolean and derive marker emission directly from p.state.
Emit voip_settings="video" only when state is UpgradeRequestV2, ensuring
downgrade states never receive the marker; update all callers accordingly.

In `@wacore/src/voip/e2e_srtp.rs`:
- Around line 176-203: Extend unprotect_srtcp to return the authenticated 31-bit
SRTCP index alongside plaintext, then in wacore/src/voip/session.rs lines
227-235 add per-sender-SSRC highest-index and replay-bitmap state. Update the
receive path at lines 368-372 to validate and mutate replay state only after
authentication, rejecting duplicate or stale indices while accepting new packets
and committing the updated window.

In `@wacore/src/voip/h264.rs`:
- Around line 320-325: Update the H264 packet handling around flush_on,
queue_ready, and ready.pop_front so multiple completed access units produced by
one push are not stranded when no subsequent packet arrives. Preserve delivery
ordering while returning all newly completed AUs, or expose a queue-draining
interface that VideoPipeline can consume immediately instead of limiting push to
one result.

In `@wacore/src/voip/rtp.rs`:
- Around line 407-415: Update VideoRtpStream::new to reject ts_stride == 0 at
construction, matching the existing setter invariant; preserve normal
initialization for positive strides and use the established rejection mechanism
from the setter.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository UI (base), Organization UI (inherited)

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: 1c67c7f4-2eed-4b82-a073-b8a0cc599023

📥 Commits

Reviewing files that changed from the base of the PR and between 865d3f2 and 4fae965.

📒 Files selected for processing (25)
  • examples/voip-cli/src/main.rs
  • examples/voip-cli/src/video.rs
  • src/handlers/call.rs
  • src/voip/facade.rs
  • src/voip/mod.rs
  • src/voip/transport.rs
  • src/voip/video.rs
  • wacore/benches/voip_benchmark.rs
  • wacore/src/stanza/call.rs
  • wacore/src/types/call.rs
  • wacore/src/voip/demux.rs
  • wacore/src/voip/driver.rs
  • wacore/src/voip/e2e_srtp.rs
  • wacore/src/voip/engine.rs
  • wacore/src/voip/h264.rs
  • wacore/src/voip/hbh_srtp.rs
  • wacore/src/voip/mod.rs
  • wacore/src/voip/registry.rs
  • wacore/src/voip/relay_parse.rs
  • wacore/src/voip/rtcp.rs
  • wacore/src/voip/rtp.rs
  • wacore/src/voip/session.rs
  • wacore/src/voip/ssrc.rs
  • wacore/src/voip/stun.rs
  • wacore/src/voip/testdata/kats.json

Comment thread examples/voip-cli/src/main.rs
Comment thread examples/voip-cli/src/main.rs
Comment thread examples/voip-cli/src/main.rs
Comment thread examples/voip-cli/src/video.rs
Comment thread examples/voip-cli/src/video.rs
Comment thread wacore/src/stanza/call.rs
Comment thread wacore/src/voip/e2e_srtp.rs Outdated
Comment thread wacore/src/voip/h264.rs
Comment thread wacore/src/voip/rtp.rs Outdated
Comment thread src/handlers/call.rs Outdated

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (2)
wacore/src/voip/engine.rs (1)

732-739: 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

disable_video() should re-arm keyframe_required on downgrade. A plain downgrade→re-upgrade path can otherwise resume without forcing a fresh IDR, while the gated-resume path already handles that case. Set v.keyframe_required = true here so the state machine stays consistent.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@wacore/src/voip/engine.rs` around lines 732 - 739, Update disable_video to
set v.keyframe_required = true alongside deactivating the video plane, ensuring
every downgrade requires a fresh keyframe when video is re-enabled while
preserving the existing idempotent behavior.
wacore/src/voip/registry.rs (1)

221-255: 🗄️ Data Integrity & Integration | 🟡 Minor | ⚡ Quick win

Guard the video-path helpers with generation
send_call_event, send_video_ctl, set_is_video, and run_video_teardown still key only on call_id, so a delayed stale path can act on a same-id replacement. Thread generation through these methods to match the existing ABA guards.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@wacore/src/voip/registry.rs` around lines 221 - 255, Update send_call_event,
send_video_ctl, set_is_video, and run_video_teardown to accept and validate the
call generation alongside call_id before accessing or mutating registry state.
Propagate the generation through every caller and preserve the existing behavior
only when both identifiers match, preventing delayed stale paths from affecting
same-id replacements.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Outside diff comments:
In `@wacore/src/voip/engine.rs`:
- Around line 732-739: Update disable_video to set v.keyframe_required = true
alongside deactivating the video plane, ensuring every downgrade requires a
fresh keyframe when video is re-enabled while preserving the existing idempotent
behavior.

In `@wacore/src/voip/registry.rs`:
- Around line 221-255: Update send_call_event, send_video_ctl, set_is_video, and
run_video_teardown to accept and validate the call generation alongside call_id
before accessing or mutating registry state. Propagate the generation through
every caller and preserve the existing behavior only when both identifiers
match, preventing delayed stale paths from affecting same-id replacements.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository UI (base), Organization UI (inherited)

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: dc8422be-cea2-4e90-b8ba-cf670c2025bd

📥 Commits

Reviewing files that changed from the base of the PR and between 4fae965 and b5d4d40.

📒 Files selected for processing (13)
  • examples/voip-cli/src/main.rs
  • examples/voip-cli/src/video.rs
  • src/handlers/call.rs
  • src/voip/facade.rs
  • wacore/src/stanza/call.rs
  • wacore/src/voip/driver.rs
  • wacore/src/voip/e2e_srtp.rs
  • wacore/src/voip/engine.rs
  • wacore/src/voip/h264.rs
  • wacore/src/voip/hbh_srtp.rs
  • wacore/src/voip/registry.rs
  • wacore/src/voip/rtp.rs
  • wacore/src/voip/session.rs

Comment thread src/handlers/call.rs Outdated

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: eb0365053e

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread wacore/src/stanza/call.rs Outdated
Comment thread src/handlers/call.rs Outdated

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (3)
src/handlers/call.rs (2)

217-257: 🩺 Stability & Availability | 🟠 Major | ⚡ Quick win

Hold the transition permit until every committed effect completes.

permit.send(...) consumes the permit immediately, releasing the reservation before orientation, video state, controls, and the Enabled announcement finish. Another handler can overtake this transition and then have its state overwritten. Publish without consuming the permit and drop it only after the entire committed block.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/handlers/call.rs` around lines 217 - 257, Update the transition handling
around the event_permit and the committed video-state effects so permit.send
publishes the CallEvent without consuming the permit, retaining the permit
through orientation updates, registry video-state/control changes, and the
UpgradeAccept Enabled announcement. Explicitly drop the permit only after all
these effects complete, while preserving the existing receiver-closed warning
behavior.

177-214: 🩺 Stability & Availability | 🔴 Critical | 🏗️ Heavy lift

Do not apply a stale video stanza to a replacement call.

The permit captures the old entry, but send_node(...).await allows a same-ID replacement. Subsequent teardown, state, and control calls resolve by call_id and can mutate or tear down the new generation. Bind the permit to its generation and generation-guard every post-ack effect.

Also applies to: 226-257

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/handlers/call.rs` around lines 177 - 214, Prevent stale video stanzas
from affecting replacement calls after the await in the handling flow around
reserve_call_event and send_node. Bind the event permit to the original call
generation, then generation-guard every post-ack effect, including
run_video_teardown, set_is_video, state updates, and control operations in the
related 226–257 block. If the generation no longer matches, skip those effects.
wacore/src/voip/registry.rs (1)

229-238: 🩺 Stability & Availability | 🟠 Major | ⚡ Quick win

Consume the teardown hook before invoking it.

Cloning leaves the hook installed, so duplicate rejects and the later CallEntry drop invoke it again. The callback has no idempotency contract; take() it under the lock, then invoke it off-lock.

Proposed fix
-        let hook = self
+        let hook = self
             .inner
             .lock()
             .expect("registry lock poisoned")
-            .get(call_id)
-            .and_then(|entry| entry.video_teardown.clone());
+            .get_mut(call_id)
+            .and_then(|entry| entry.video_teardown.take());
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@wacore/src/voip/registry.rs` around lines 229 - 238, Update
Registry::run_video_teardown to remove the video_teardown hook from the
CallEntry under the registry lock using take() rather than cloning it, then
invoke the extracted hook outside the lock. Preserve the existing behavior when
no hook is installed.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Outside diff comments:
In `@src/handlers/call.rs`:
- Around line 217-257: Update the transition handling around the event_permit
and the committed video-state effects so permit.send publishes the CallEvent
without consuming the permit, retaining the permit through orientation updates,
registry video-state/control changes, and the UpgradeAccept Enabled
announcement. Explicitly drop the permit only after all these effects complete,
while preserving the existing receiver-closed warning behavior.
- Around line 177-214: Prevent stale video stanzas from affecting replacement
calls after the await in the handling flow around reserve_call_event and
send_node. Bind the event permit to the original call generation, then
generation-guard every post-ack effect, including run_video_teardown,
set_is_video, state updates, and control operations in the related 226–257
block. If the generation no longer matches, skip those effects.

In `@wacore/src/voip/registry.rs`:
- Around line 229-238: Update Registry::run_video_teardown to remove the
video_teardown hook from the CallEntry under the registry lock using take()
rather than cloning it, then invoke the extracted hook outside the lock.
Preserve the existing behavior when no hook is installed.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository UI (base), Organization UI (inherited)

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: 86a82fe0-2763-4d9a-8845-f468d8237bdf

📥 Commits

Reviewing files that changed from the base of the PR and between b5d4d40 and a6fe301.

📒 Files selected for processing (2)
  • src/handlers/call.rs
  • wacore/src/voip/registry.rs

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: f9b3fb4ee9

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread wacore/src/voip/driver.rs
Comment thread wacore/src/voip/registry.rs Outdated
Comment thread wacore/src/voip/engine.rs Outdated
Comment thread wacore/src/voip/engine.rs Outdated
Comment thread src/handlers/call.rs

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (2)
src/handlers/call.rs (1)

193-203: 🩺 Stability & Availability | 🟠 Major | ⚡ Quick win

Bound outbound sends while holding the transition permit.

send_node can remain pending, leaving event_permit reserved indefinitely. Every subsequent video transition then fails reservation and reverts. Add timeout/cancellation handling for both the typed ACK and Enabled announcement, releasing the permit on failure.

Also applies to: 263-277

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/handlers/call.rs` around lines 193 - 203, Bound the asynchronous send
operations in the video transition handling, including the typed ACK in
build_call_video_ack and the Enabled announcement in the corresponding branch
around those sends. Apply the existing timeout or cancellation mechanism to
client.send_node so pending sends fail, and ensure every failure path releases
event_permit before returning or reverting the transition.
src/voip/facade.rs (1)

1574-1592: 🩺 Stability & Availability | 🟠 Major | ⚡ Quick win

Stop on a lost generation before sending video signals. set_is_video() can return false after a same-call-id replacement lands, but the code still sends VideoControl and the <video> stanzas. Return immediately on false so a stale handle doesn't keep signaling the newer call.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/voip/facade.rs` around lines 1574 - 1592, Update the video setup flow
around set_is_video so it checks the returned boolean and returns immediately
when the generation is no longer active. Perform this validation before
send_control, ensuring stale handles do not emit VideoControl or video stanzas,
while preserving the existing successful path.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@src/handlers/call.rs`:
- Around line 279-285: Update the transition commit flow around dispatch_call in
the call handler so Event::IncomingCall is dispatched while event_permit is
still held. Move the permit release after publication, preserving the existing
dispatch condition and ensuring subsequent transitions cannot publish ahead of
the current one.

In `@wacore/src/voip/registry.rs`:
- Around line 293-310: Update send_video_ctl so only
VideoControl::SetOrientation uses best-effort try_send; route all
state-transition controls, including Enable and Disable, through a non-evicting
send operation that preserves queued commands. Ensure the video_ctl channel and
its send path no longer allow force_send to evict pending transitions.

---

Outside diff comments:
In `@src/handlers/call.rs`:
- Around line 193-203: Bound the asynchronous send operations in the video
transition handling, including the typed ACK in build_call_video_ack and the
Enabled announcement in the corresponding branch around those sends. Apply the
existing timeout or cancellation mechanism to client.send_node so pending sends
fail, and ensure every failure path releases event_permit before returning or
reverting the transition.

In `@src/voip/facade.rs`:
- Around line 1574-1592: Update the video setup flow around set_is_video so it
checks the returned boolean and returns immediately when the generation is no
longer active. Perform this validation before send_control, ensuring stale
handles do not emit VideoControl or video stanzas, while preserving the existing
successful path.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository UI (base), Organization UI (inherited)

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: d23d6cb6-193d-46b8-b628-19125d12a963

📥 Commits

Reviewing files that changed from the base of the PR and between a6fe301 and d97580a.

📒 Files selected for processing (6)
  • src/handlers/call.rs
  • src/voip/facade.rs
  • wacore/src/stanza/call.rs
  • wacore/src/voip/driver.rs
  • wacore/src/voip/engine.rs
  • wacore/src/voip/registry.rs

Comment thread src/handlers/call.rs Outdated
Comment thread wacore/src/voip/registry.rs Outdated

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
src/handlers/call.rs (1)

183-225: 🔒 Security & Privacy | 🟠 Major | ⚡ Quick win

Keep refusal teardown independent of event publication.

generation only comes from event_permit. If reservation fails, UpgradeReject/UpgradeCancel skips teardown and can leave camera capture and is_video active after the peer refuses the upgrade.

Proposed fix
 let event_permit = registry.reserve_call_event(call_id);
 let generation = event_permit.as_ref().map(|permit| permit.generation());
+let teardown_generation =
+    generation.or_else(|| registry.generation_of(call_id));
 ...
 if matches!(state, VideoState::UpgradeReject | VideoState::UpgradeCancel)
-    && let Some(generation) = generation
+    && let Some(generation) = teardown_generation
 {

Add coverage with the event permit unavailable or already reserved.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/handlers/call.rs` around lines 183 - 225, Make refusal teardown
independent of event permit reservation in the call video transition handler:
for UpgradeReject and UpgradeCancel, always run the appropriate local teardown
and clear is_video even when reserve_call_event returns None or the event is
already reserved. Obtain or pass the needed call generation through a path that
remains available without event publication, while preserving the existing ack
and dispatch behavior.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@src/handlers/call.rs`:
- Around line 255-290: Reorder the logic in the video-state handler so
orientation, is_video, and Enable effects are applied before sending
CallEvent::VideoStateChanged. Retain event_permit through those updates, then
publish the event and preserve the existing warning when delivery fails.

In `@src/voip/facade.rs`:
- Around line 1624-1628: Add telemetry or structured logging to the
send_state(VideoState::Enabled, None) failure branch, capturing the handshake
failure and relevant available context before teardown_local_video and returning
the error. Keep the existing rollback and error propagation behavior unchanged.

---

Outside diff comments:
In `@src/handlers/call.rs`:
- Around line 183-225: Make refusal teardown independent of event permit
reservation in the call video transition handler: for UpgradeReject and
UpgradeCancel, always run the appropriate local teardown and clear is_video even
when reserve_call_event returns None or the event is already reserved. Obtain or
pass the needed call generation through a path that remains available without
event publication, while preserving the existing ack and dispatch behavior.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository UI (base), Organization UI (inherited)

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: 931cc942-5a15-4540-ba44-6ee980f659e0

📥 Commits

Reviewing files that changed from the base of the PR and between d97580a and 85ef783.

📒 Files selected for processing (2)
  • src/handlers/call.rs
  • src/voip/facade.rs

Comment thread src/handlers/call.rs Outdated
Comment thread src/voip/facade.rs
@jlucaso1
jlucaso1 merged commit d37b175 into main Jul 16, 2026
17 checks passed
@jlucaso1
jlucaso1 deleted the feat/voip-video branch July 16, 2026 12:30

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: a904536d18

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread wacore/src/voip/engine.rs
Comment on lines +919 to +923
if let Some(video_ssrc) = video_ssrc
&& requests_keyframe(&summary.feedback, video_ssrc)
&& let Some(video) = m.video.as_mut()
{
video.keyframe_required = true;

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Preserve RTCP feedback under relay backpressure

This new PLI/FIR handling only runs if the RTCP packet reaches the engine, but in the native relay path relay_read_pump still drops every full-queue packet except STUN (src/voip/transport.rs:394-396). During video congestion—exactly when keyframe feedback matters—a peer's RTCP PLI/FIR can be discarded before this sets keyframe_required, leaving the remote side on corrupt/black video until the encoder's next periodic IDR; RTCP feedback should be preserved as control traffic rather than shed with media.

Useful? React with 👍 / 👎.

Comment thread wacore/src/voip/h264.rs
Comment on lines +318 to +320
t if (1..=23).contains(&t) => {
self.drop_partial_fu();
self.append_nal(payload);

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Drop incomplete H.264 AUs after sequence gaps

When an AU is packetized as separate single-NAL RTP packets (for example SPS, PPS, then IDR) and any non-FU packet is lost or reordered, this branch appends later NALs without checking that seq is contiguous; a following marker then flushes a partial AU as if it were complete. That can feed sinks an IDR missing SPS/PPS or slices, causing decoder errors/corruption until a later keyframe, so track gaps across all packets in an AU and discard/resync instead of emitting the partial frame.

Useful? React with 👍 / 👎.

Comment thread wacore/src/voip/engine.rs
Comment on lines +735 to +738
pub fn disable_video(&mut self) {
if let Some(v) = self.media.as_mut().and_then(|m| m.video.as_mut()) {
v.active = false;
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Reset inbound video reassembly on disable

When Disable arrives after some inbound H.264 packets were accepted, this only flips active to false; the preserved VideoPipeline also preserves its depacketizer buffers. A quick re-enable can then make the next forward RTP timestamp flush the old partial AU to the newly attached sink before the new negotiation's frames, so the inbound reassembly/ready queue should be cleared when disabling video while still preserving the SRTP counters.

Useful? React with 👍 / 👎.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants