Files
screen_cast/ios
fegger c43dd3ca4a feat(ios): native receiver app (Swift, min iOS 17)
A native iOS receiver so an iPhone can act as the second receiver, speaking the existing signaling + RTP protocol (no C++ changes) and mirroring the Android receiver (Phase 8) source-to-source.

- RTP core (header/packet, jitter buffer, H.264 depacketizer) ported from the Android receiver
- BSD-socket signaling server (dual-stack, most-recent-peer, never-throwing sends) + NSBonjourServices
- VideoToolbox H.264 decode (in-band SPS/PPS, real-time, rebuilds on size change) -> AVSampleBufferDisplayLayer
- PLI keyframe recovery (500 ms) + pendingOffer for late surface attach
- XcodeGen project + bootstrap.sh; XCTest port of the Android suite + new coverage
- .gitignore for generated artifacts; CHANGELOG; PHASES + MEMORY updated

Status: authored; on-device validation pending a Mac + Xcode 26 + iPhone 16.
2026-09-10 21:31:53 +02:00
..

screencast iOS receiver

A native iOS receiver app (Swift) so an iPhone can act as a second receiver. It speaks the existing signaling + RTP protocol unchanged — no C++ changes — mirroring the Android receiver (Phase 8) source-to-source.

  • Min iOS: 17 (validated on iPhone 16 / iOS 26.6.1)
  • No third-party dependencies — only Apple system frameworks (SwiftUI, AVFoundation, VideoToolbox, CoreMedia, BSD sockets).

Pipeline

Bonjour advertise (_screencast._tcp) + TCP signaling server (offer → answer)
  UDP RTP → jitter buffer (16 pkt / 60 ms) → depacketize (single-NAL + FU-A)
  → VideoToolbox H.264 (in-band SPS/PPS) → AVSampleBufferDisplayLayer (letterbox)

Recovery matches the C++ receiver: a damaged frame is dropped and a PLI (rate-limited to one per 500 ms) asks the sender for a keyframe.

Layout

Receiver/
  App/        SwiftUI app, video surface (AVSampleBufferDisplayLayer), controller
  Rtp/        RtpHeader, RtpPacket, JitterBuffer, H264Depacketizer, Avcc, NAL extraction
  Signaling/  SignalingMessage (JSON), LineAssembler, SignalingServer (BSD sockets)
  Decode/     RenderSink, H.264 format description, VideoToolbox decoder
  Support/    UdpTransport (poll-based), LocalAddress
  Pipeline/   ReceiverPipeline (coordinates everything)
ReceiverTests/
  XCTest port of the Android JVM tests + added coverage (signaling, line
  framing, AVCC, NAL extraction)

Build and test (on a Mac with Xcode 26)

./bootstrap.sh            # installs XcodeGen, generates the project, builds for device
./bootstrap.sh test       # runs the unit tests on the simulator

bootstrap.sh downloads XcodeGen from the GitHub release into tools/ (no Homebrew). Override the simulator with IOS_DEST="platform=iOS Simulator,name=…".

Install on a device

Free provisioning (or your team) is set in Xcode — this can't be scripted:

  1. Open Receiver.xcodeproj.
  2. Select the Receiver target → Signing & Capabilities → set your Apple ID (a 7-day development certificate is fine for sideloading).
  3. Run to the iPhone (USB, trust the computer).

The first run prompts for Local Network access — allow it, or the sender will never discover the phone (silent failure, like the Android PROTOCOL_DNS_SD bug).

Stream to the phone

On the sender (Linux desktop):

screencast --send --peer <phone-ip>:5005      # target the phone directly
screencast --discover                          # or list receivers; the phone shows up

Expected: the phone shows the captured desktop, letterboxed to its screen, with PLI recovery on Wi-Fi loss.

Testing notes

The pure protocol core (RTP, jitter, depacketizer, signaling JSON, line framing, AVCC, NAL extraction) is unit-tested in the simulator. The VideoToolbox decode path and the local-network/Bonjour flow are on-device validation items — the highest-risk parts to verify on the borrowed Mac + iPhone.