A Flutter plugin for iOS that uses ReplayKit to record the device screen and provides low-frequency preview frames (JPEG) for UI and analytics/computer vision. Designed for performance, clarity, and maintainability.
- iOS only (Swift 5)
- Minimum iOS: 12.0
- Target devices: iPhone 11 and newer recommended
- Start/stop screen capture (ReplayKit) from Flutter
- Pull-based preview frames (JPEG) at up to a configured max FPS
- Optional video recording (via a flag in
startScreenCapture) to an.mp4file (HEVC by default; H.264 fallback) - Optional save to Photos (camera roll) upon recording completion
- Event stream for lifecycle and recording completion
- Correct video timing and aspect ratio (writer starts at first sample PTS; output size respects source aspect)
Add to your pubspec.yaml (for apps using this plugin):
dependencies:
ios_screen_recording_flutter_plugin: ^0.0.1iOS setup in your app:
- Ensure minimum iOS 12 in your app target.
- If you plan to save recordings to Photos, add to your app
Info.plist:
<key>NSPhotoLibraryAddUsageDescription</key>
<string>Allow saving screen recordings to your Photos.</string>final plugin = IosScreenRecordingFlutterPlugin();
// Start capture; set recordVideo to true to record while capturing
await plugin.startScreenCapture(
previewConfig: const PreviewConfig(previewMaxFps: 5, previewMaxDimension: 320, jpegQuality: 0.5),
recordVideo: true,
recordingConfig: const RecordingConfig(
videoCodec: 'hevc', // 'hevc' | 'h264'
videoResolution: '540p', // '540p' | '720p' | 'screenNative'
videoFps: 24,
saveToCameraRoll: true,
),
);
// Pull latest frame at ~5 fps
final frame = await plugin.getLatestFrame(timeoutMs: 1200);
// frame.jpegBytes, frame.width, frame.height, frame.pixelRatio, frame.timestampMs
// Stop capture (and finalize recording if enabled)
await plugin.stopScreenCapture();
// Listen for events
plugin.events.listen((event) {
if (event['type'] == 'screen_recording') {
final data = Map<String, dynamic>.from(event['data'] as Map);
final url = data['videoFileUrl'] as String?;
final saved = data['savedToCameraRoll'] as bool?;
final assetId = data['savedAssetLocalId'] as String?;
}
});Future<void> startScreenCapture({ PreviewConfig previewConfig, bool recordVideo = false, RecordingConfig? recordingConfig })Future<void> stopScreenCapture()Future<PreviewFrame?> getLatestFrame({ int timeoutMs = 1200 })Stream<Map<String, dynamic>> get events
class PreviewConfig {
const PreviewConfig({
this.previewMaxFps = 5,
this.previewMaxDimension = 320,
this.jpegQuality = 0.5,
});
final int previewMaxFps; // 1..30
final int previewMaxDimension; // longest side in px (scaled image)
final double jpegQuality; // 0.1..0.95
}
class RecordingConfig {
const RecordingConfig({
this.videoCodec = 'hevc', // 'hevc' | 'h264'
this.videoResolution = '540p', // '540p' | '720p' | 'screenNative'
this.videoFps = 24, // 10..60
this.videoBitrateKbps, // optional override
this.saveToCameraRoll = false, // save result to Photos when finished
});
final String videoCodec;
final String videoResolution;
final int videoFps;
final int? videoBitrateKbps;
final bool saveToCameraRoll;
}
class PreviewFrame {
final Uint8List jpegBytes; // compressed JPEG preview
final int width; // scaled width in px
final int height; // scaled height in px
final double pixelRatio; // UIScreen scale at capture time
final int timestampMs; // ms epoch
}{ type: 'started' }— capture started{ type: 'stopped' }— capture stopped{ type: 'screen_recording', data: { videoFileUrl, videoFileStartTimestamp, savedToCameraRoll?, savedAssetLocalId? } }— recording completed
- Preview frames (pull-only):
- maxFps: 5
- maxDimension: 320 px (longest side)
- jpegQuality: 0.5
getLatestFrame(timeoutMs)returns the latest cached frame; if no frame yet, it waits up tomax(1200ms, 1.5×frameInterval).
- Recording:
- codec: HEVC by default (fallback to H.264 if needed)
- resolution: 540p by default; 720p and native screen size supported
- fps: 24
- bitrate: HEVC ~1.8 Mbps; H.264 ~3.0 Mbps (override via
videoBitrateKbps) - audio: off
- storage: app Documents dir (
screen_<epoch>.mp4) - Photos save: set
saveToCameraRoll: true(requires Info.plist key) - Video timing/aspect: writer session starts at first sample PTS; output size is computed from the first sample and target long-edge while preserving aspect ratio.
- Shows preview frames at ~5 FPS.
- Toggle to record video on start (and save to Photos).
- Start/Stop buttons are disabled appropriately during capture:
- Start disabled while capturing
- Stop disabled when not capturing
Run on a physical iOS device:
cd example
flutter run -d <your-ios-device-id>- ReplayKit may not capture protected/DRM content.
- Preview frames are compressed JPEGs for low-FPS UI/analytics; reading Texture pixels for analytics is not efficient.
- Avoid requesting frames faster than
previewMaxFps.
- Black/very long videos: fixed by starting the writer session at the first sample PTS and using sourceFormatHint.
- Wrong aspect ratio: fixed by computing output size from the first sample while preserving aspect.
- Photos save failed: ensure
NSPhotoLibraryAddUsageDescriptionis set and user granted permissions.
See LICENSE.