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.
This commit is contained in:
2026-09-10 21:31:53 +02:00
parent 4f93c7cd20
commit c43dd3ca4a
31 changed files with 2282 additions and 1 deletions
+44
View File
@@ -7,6 +7,50 @@ bottom of this file.
## Project state
- **Phase 9 in progress: iOS receiver app** (`ios/`, Swift, min iOS 17):
iPhone as a second receiver. Speaks the same signaling+RTP protocol — no C++
changes. Mirrors the Android receiver (Phase 8) source-to-source.
- Scaffolding: XcodeGen `project.yml` (Info.plist carries
`NSLocalNetworkUsageDescription` + `NSBonjourServices: _screencast._tcp`) +
`bootstrap.sh` (downloads XcodeGen from the GitHub release, no Homebrew;
generates the project; builds/tests via xcodebuild). `ios/README.md` has
build/install/device steps.
- App (`Receiver/App`): SwiftUI + `AVSampleBufferDisplayLayer` (`.resizeAspect`
= letterbox — the same "size the surface, not a transform" lesson as
Android); `ReceiverController` (ObservableObject) drives start/stop on
scenePhase.
- Protocol core (`Receiver/Rtp`, `Receiver/Signaling`): RtpHeader/RtpPacket/
JitterBuffer/H264Depacketizer ported source-to-source; SignalingMessage
(JSONSerialization) + LineAssembler + SignalingServer (BSD sockets,
dual-stack, most-recent-peer-wins, MSG_NOSIGNAL sends that never throw);
`AvccConverter` (Annex-B ↔ AVCC) + `NalExtractor` (SPS/PPS) are new for the
VideoToolbox path.
- Decode (`Receiver/Decode`): `H264FormatDescription` (Core Foundation H.264
config recipe) + `H264VideoToolboxDecoder` (in-band SPS/PPS → session;
`kVTDecompressionPropertyKey_RealTime`; session recreated on size change;
the output callback may run on a worker thread, so state is lock-guarded)
`AVSampleBufferRenderSink` (AVSampleBufferDisplayLayerSession).
- Support (`Receiver/Support`): `UdpTransport` (poll-based recv so close() can't
strand a blocked recvfrom) + `LocalAddress` (getifaddrs for the --peer hint).
- Pipeline (`Receiver/Pipeline`): ReceiverPipeline — one serial queue for all
state + decode; reader thread only polls/receives UDP; pendingOffer for late
surface attach (+ PLI); PLI rate-limited 500 ms; first-frame flag; video
size tracked (never reports a placeholder before the first real keyframe —
the Android startup-squish lesson).
- Tests (`ios/ReceiverTests`): the 25 Android JVM tests ported to XCTest plus
new coverage for signaling JSON, line framing, AVCC, NAL extraction.
- `.gitignore` excludes the generated `ios/Receiver.xcodeproj/`, `ios/tools/`,
`ios/Receiver/Info.plist` (the project.yml `info` block is the source of
truth).
- **NOT YET VALIDATED** (this box is Linux, no Xcode/iOS SDK): nothing here
compiles or runs. 9.5 needs a Mac with Xcode 26 + the iPhone 16.
Highest-risk on-device items: (1) local-network + Bonjour consent
(NSBonjourServices must be `_screencast._tcp`; iOS 26 tightened the prompt),
(2) the H264FormatDescription CF ownership recipe (a bug crashes on the
first keyframe — loud, not silent), (3) VideoToolbox decode →
AVSampleBufferDisplayLayer render. Run `ios/bootstrap.sh test` first on the
Mac.
- **Phase 8 done: Android receiver app** (`android/`, Kotlin, minSdk 30,
app id `screen_cast.receiver`): phone as second receiver (screen → HDMI via
USB-C DP-alt-mode). Speaks the existing signaling+RTP protocol — no C++