Record anything on iOS to video — an AR scene, the camera, or any view — with synced audio, without ReplayKit's permission prompt.
SurfaceRecorderSDK is a small, Swift-concurrency-native recording SDK. You give it a source (RealityKit AR, the device camera, or a UIKit/SwiftUI view) and it writes an MP4 — frames recycled from a pool, audio time-synced to the video clock, the whole pipeline driven by a single actor.
(GIF placeholder: recording an AR scene with a live FPS / CPU / MEM HUD)
import SurfaceRecorderSDK
let recorder = SurfaceRecorder(
source: ARViewFrameSource(arView), // or CameraFrameSource(), UIViewFrameSource(window), …
audio: .microphone // or .none (default — no permission prompt)
)
try await recorder.start()
// … user does stuff …
let url = try await recorder.stop() // MP4 in the temp dir, ready to share or save- 🎥 Four sources, one recorder — AR (camera + 3D), AR camera-only, the device camera, or any view.
- 🚫 No ReplayKit prompt — in-app capture of your own content; nothing to authorize.
- ⚡ GPU-fast AR —
ARViewFrameSourcetapspostProcessand never touches the CPU rasterizer. - 🎙 Audio that lines up — microphone capture with
CMSampleBuffertime-correction andAVAudioSession/ARSessioncoexistence. Off by default — no mic prompt unless you ask. - 📐 Resolution cap — downscale large screens to keep capture cheap.
- 📊 Live perf readout —
SurfacePerformanceMonitor(FPS / CPU / memory) you can show on screen. - 🧱 Swift 6, async-native — actors,
AsyncSequence, strict concurrency.
Swift Package Manager:
.package(url: "https://github.com/NazarKozak/SurfaceRecorderSDK.git", from: "0.1.0")…and add "SurfaceRecorderSDK" to your target's dependencies. Requires iOS 17+.
Tracking the latest instead of a release? Use
branch: "main".
Everything records through a FrameSource. Pick the one that matches what you want on tape:
| Source | Captures | How | Cost |
|---|---|---|---|
ARViewFrameSource |
RealityKit AR: camera + 3D content | ARView.renderCallbacks.postProcess → Metal blit |
🟢 GPU |
RealityKitFrameSource |
AR camera feed only (no 3D/UI) | zero-copy ARFrame buffer, GPU rotate/convert |
🟢 GPU |
CameraFrameSource |
a plain device camera (back/front), with preview | AVCaptureSession video output |
🟢 GPU |
UIViewFrameSource |
any UIView / the whole screen (UIKit + SwiftUI) | single-pass drawHierarchy, pooled buffers |
🟡 CPU |
// AR scene — camera + 3D, recommended for ARKit/RealityKit.
SurfaceRecorder(source: ARViewFrameSource(arView), audio: .microphone)
// AR camera feed only (clean), with rotation control.
SurfaceRecorder(source: RealityKitFrameSource(arView, orientation: .portrait))
// The device camera (like a camera app). Expose `.session` to show a preview.
let camera = CameraFrameSource(position: .back, fps: 60)
camera.startRunning() // drives the preview
SurfaceRecorder(source: camera)
// Any view or the key window — screen capture, NO ReplayKit prompt.
SurfaceRecorder(source: UIViewFrameSource(window, maxDimension: 1080))UIViewFrameSource renders your app's own view hierarchy with drawHierarchy, so it never
triggers the system screen-recording prompt. Trade-off vs ReplayKit: it captures only your app
(not other apps or system UI) — exactly what an in-app recorder wants.
drawHierarchy runs on the main thread and its cost scales with output pixels, so use
maxDimension: to cap resolution on large screens. To also capture Metal-backed content
(an ARView) in the same pass, pass afterScreenUpdates: true (heavier).
Audio is opt-in — the recorder defaults to .none, so nothing prompts the user:
SurfaceRecorder(source: …, audio: .microphone) // mic, time-synced to the video clock
SurfaceRecorder(source: …, audio: .none) // silent (default)The microphone path manages AVAudioSession (interruptions, route changes) and re-times each
sample buffer to the video session clock, so audio and video stay in lockstep even alongside ARKit.
stop() returns the MP4 URL in the temp directory. Move it where you like, or hand it straight
to a ShareLink:
let url = try await recorder.stop()
ShareLink(item: url) // AirDrop / Save Video / FilesSurfaceRecorderSDK is built to be cheap:
- GPU AR path —
ARViewFrameSourceblits the composited render texture into a pixel buffer; no CPU rasterization. - Zero-copy AR camera —
RealityKitFrameSourcehands theARFramebuffer straight to a GPU convert/rotate. - Single-pass UI capture —
UIViewFrameSourcerenders into the pixel buffer's backingCGContext, no intermediateUIImage. - Recycled buffers — every source pulls from a
CVPixelBufferPool. - Serialized, lock-free — one
actorowns the writer; no manual locking.
Watch it live with SurfacePerformanceMonitor:
@State private var perf = SurfacePerformanceMonitor()
// …
.onAppear { perf.start() }
Text(perf.summary) // "60 fps · 8% cpu · 124 MB"Open Demo/SurfaceRecorderSDKDemo.xcodeproj in Xcode, pick a simulator or your device, and run. The demo has:
- three tabs — Screen, ARKit, Camera;
- an FPS selector (30 / 60 / Max) and a microphone toggle;
- a gallery that saves recordings to Documents and plays them back;
- a live FPS / CPU / MEM overlay.
AR and camera capture need a real device (the simulator has no camera).
The project depends on the local package and uses Xcode's file-system-synchronized groups —
drop a .swift file into Demo/Sources/ and it's picked up automatically. (swift build only
compiles the library, not the iOS demo.)
| ReplayKit | ARVideoKit | SurfaceRecorderSDK | |
|---|---|---|---|
| No permission prompt (in-app capture) | ❌ | ✅ | ✅ |
RealityKit ARView (camera + 3D) |
❌ | ✅ | |
| Plain device camera + preview | — | — | ✅ |
| Any UIKit/SwiftUI view / screen | ❌ | ❌ | ✅ |
| Audio time-synced to the video clock | ✅ | ✅ | |
| Swift 6 / async-native | ❌ | ❌ | ✅ |
| Maintained for the RealityKit era | ✅ | ✅ |
- iOS 17+
- Swift 6 / Xcode 16+
- For AR/camera capture: a real device. Add
NSCameraUsageDescription(andNSMicrophoneUsageDescriptionif you record audio) to your Info.plist.
- GPU AR recording — camera + 3D (
ARViewFrameSource) - AR camera-only, zero-copy with orientation control (
RealityKitFrameSource) - Device camera with preview (
CameraFrameSource) - No-permission screen/UI capture, single-pass + resolution cap (
UIViewFrameSource) - Mic audio with time-sync +
AVAudioSession/ARKit coexistence - Live performance monitor
- GPU overlay compositing — UI/HUD over AR at full frame rate (
ARViewFrameSource.setOverlay) - GPU overlay for
CameraFrameSourcetoo -
ARSCNViewFrameSource(SceneKit) + "migrate from ARVideoKit" guide - Photo / GIF / Live Photo capture
- visionOS, HEVC, pause/resume
MIT — see LICENSE.