Skip to content

iOS ​

A SwiftUI app with Liquid Glass, on the webrtc-sdk 150.7871.01 binary. iOS 26 or newer, built with Xcode 26.

Screenshot to add
An iPhone on the home screen (or in another app) with the call in the system picture-in-picture window, showing the other person's video.
File: docs/public/media/call-ios.png

Open ios/WebRTCDemo.xcodeproj, pick the WebRTCDemo scheme and run on a device. The camera and screen sharing need real hardware.

Where things are ​

Files in ios/WebRTCDemo. Most of them are also compiled into the Mac app.

FileWhat it does
CallViewModel.swift@Observable call state. Owns LocalMedia and the 1:1 or group engine.
LocalMedia.swiftFactory, local tracks, the single RTCVideoSource, sharing, audio session, VP8 first
PeerConnectionClient.swiftWebRTCClient, the 1:1 engine: one peer connection and data channel
GroupCallClient.swiftThe group engine: SFU WebSocket, publish and subscribe connections
SignalingSocket.swiftJSON over URLSessionWebSocketTask. Used by both engines.
FrameEncryption.swiftE2EE key provider and frame cryptors
EffectsProcessor.swiftVision + Core Image, as a proxy capturer delegate
PictureInPicture.swiftSystem PiP through an AVSampleBufferDisplayLayer
VideoView.swiftRTCMTLVideoView wrapper and the placeholder snapshotter
CallView.swift, GroupCallView.swiftThe call screens
WebRTCDemoScreenBroadcast/The ReplayKit broadcast extension

WebRTCClient calls its delegate from WebRTC threads, and the view model hops to the main queue before touching state.

Screen sharing ​

iOS only lets an app capture the whole screen, including after you leave it, through a ReplayKit broadcast upload extension. The extension runs in its own process and can't run WebRTC, so it sends the frames to the app over a Unix domain socket in the shared app group. FlutterBroadcastScreenCapturer then feeds them into the same RTCVideoSource the camera uses.

Each frame is an HTTP-style message: Content-Length, Buffer-Width, Buffer-Height and Buffer-Orientation headers, then a JPEG body. The app side of the socket is copied from the flutter-webrtc plugin. Keep its reading logic as it is: if the reader ever asks the stream for 0 bytes, iOS reports end of stream and the broadcast stops right after the first frame.

Sharing keeps going when you leave the app, which needs two things:

  • The app stays alive. UIBackgroundModes has audio and voip, and the call keeps a playAndRecord audio session active, so iOS doesn't suspend the app and its socket.
  • The encoder keeps working. iOS invalidates the VideoToolbox hardware H264 encoder in the background, so every frame would fail and the other side would see a frozen picture. iOS therefore always puts VP8 (software) first, with or without E2EE. It costs more CPU and battery.

The app and the extension must share the same App Group (group.com.ducmai.webrtc.broadcast). If you change the team or bundle ID, update it in both .entitlements files, RTCAppGroupIdentifier in Info.plist, SampleHandler.swift, and the extension bundle ID in RTCScreenSharingExtension.

The audio session ​

The app configures the audio session itself: before every call, CallViewModel.configureCallAudio() sets WebRTC's configuration to playAndRecord + voiceChat. The webrtc-sdk fork otherwise copies the session's launch category (soloAmbient), which iOS rejects together with the Bluetooth HFP option, and calls end up with no microphone and no sound. See Troubleshooting.

How it does each part ​

  • Switching sources. Camera, file and screen capturers all feed one RTCVideoSource, so the track, the sender and its cryptor never change. Switching video sources
  • E2EE. RTCFrameCryptor with the shared options.
  • Effects. A proxy RTCVideoCapturerDelegate between the camera and the source runs Vision and Core Image on Metal. Backgrounds and effects
  • Picture-in-picture. Video call PiP, 1:1 only. Picture-in-picture
  • Group calls. Every subscribe offer is parsed for a=mid and a=msid, because libwebrtc doesn't fire a new receiver event when an m-line is reused for someone else. Group calls (SFU)

Notes ​

  • WebRTC comes from the local Swift package ios/Packages/WebRTC, because the Specs manifest for this release doesn't resolve. To upgrade, change the URL and checksum in its Package.swift.
  • Info.plist allows plain HTTP (NSAllowsArbitraryLoads) so any LAN server works.