PVUI/Sources/PVUIBase/Recording/RECORDING.md
Provenance supports ReplayKit screen recording on iOS, gated behind Provenance Plus via FreemiumKit.
The feature is iOS-only (#if os(iOS)); tvOS is excluded as ReplayKit recording has platform limitations.
| File | Role |
|---|---|
PVRecordingManager.swift | @MainActor singleton wrapping RPScreenRecorder start/stop/discard |
PVEmulatorViewController+Recording.swift | VC extension with startScreenRecording(), stopScreenRecording(), discardScreenRecording(), toggleScreenRecording() — the only place that updates AppState |
RetroMenuView.swift (recordingButton) | SwiftUI button in the States tab, reads recording state from AppState.shared.emulationUIState.isRecording |
EmulationState.swift | isRecording: Bool flag on EmulationUIState — the single source of truth for SwiftUI |
PVRecordingManager is @MainActor public final class. All call sites must be on the main actor:
UIViewController method: wrap in Task { @MainActor in ... } (already done in the VC extension)startRecording() and stopRecording(presenter:) are async throws.
Do not revert to completion-handler form — that pattern is replaced by Swift concurrency.
AppState.shared.emulationUIState.isRecording is the canonical UI-observable flag.
true only inside startScreenRecording() after await PVRecordingManager.shared.startRecording() succeedsfalse inside stopScreenRecording(), discardScreenRecording(), and on any error pathPVRecordingManager.isRecording is the manager's internal guard (prevents double-start/stop) — it must not be read directly by SwiftUI viewsRetroMenuView reads AppState, not PVRecordingManager.shared.isRecordingPVRecordingManager does not inherit from NSObject.
The RPPreviewViewControllerDelegate conformance is handled by the private inner class PreviewDelegate.
The record button is wrapped in PaidFeatureView (FreemiumKit).
Do not add recording affordances outside this view without the same gating.
emulatorVC.toggleScreenRecording() (or the start/stop/discard variants)AppState.shared.emulationUIState.isRecording, not from PVRecordingManager#if os(iOS) and a PaidFeatureView if exposing UIRPScreenRecorder.shared().isAvailable returns false in the simulator — the button is hidden automatically