Реализация демонстрации экрана (Screen Sharing) в мобильном приложении
Screen sharing на мобильных платформах — принципиально другая задача, чем на десктопе. iOS и Android не дают приложению прямой доступ к буферу экрана. Нужно использовать специальные системные механизмы, каждый со своими ограничениями.
iOS: ReplayKit и Broadcast Upload Extension
На iOS захват экрана работает только через ReplayKit. Нельзя получить контент экрана напрямую из основного процесса приложения — это ограничение sandbox.
Для трансляции в реальном времени используем RPSystemBroadcastPickerView (iOS 12+), который показывает системный picker для запуска broadcast. Трансляция идёт через Broadcast Upload Extension — отдельный таргет в проекте, который запускается в собственном процессе (com.apple.broadcast-services-upload). Extension и основное приложение не имеют общей памяти — обмен данными только через App Group (shared UserDefaults, shared FileManager, или Darwin notify для сигналов).
Структура extension:
class SampleHandler: RPBroadcastSampleHandler {
override func processSampleBuffer(_ sampleBuffer: CMSampleBuffer,
with sampleBufferType: RPSampleBufferType) {
switch sampleBufferType {
case .video:
// Передаём CMSampleBuffer в WebRTC/Agora/Livekit
videoSource?.capturer(capturer, didCapture: toRTCVideoFrame(sampleBuffer))
case .audioApp:
// Системный аудио
case .audioMic:
// Микрофон (доступен только при явном разрешении в Info.plist)
}
}
}
Extension имеет лимит памяти 50 МБ (в отличие от основного приложения). Если подключить тяжёлый SDK — превысим лимит и получим NSExtensionRequestExpiredException без внятного сообщения об ошибке. Agora и Livekit предоставляют облегчённые версии SDK специально для Broadcast Extension.
Коммуникация extension → основное приложение через CFNotificationCenter (Darwin notifications): extension отправляет уведомление, основное приложение устанавливает WebRTC peer connection и начинает ретранслировать кадры.
Разрешение и FPS ограничены ReplayKit: максимум 1080p, до 60 FPS. На практике для screen sharing достаточно 720p/15fps — экраны мобильных устройств небольшие, и при меньшем FPS качество текста не страдает, зато битрейт ниже.
Android: MediaProjection API
На Android захват экрана — через MediaProjection API (Android 5.0+). Пользователь должен явно разрешить запись:
val mediaProjectionManager = getSystemService(MEDIA_PROJECTION_SERVICE) as MediaProjectionManager
startActivityForResult(
mediaProjectionManager.createScreenCaptureIntent(),
REQUEST_MEDIA_PROJECTION
)
В onActivityResult получаем Intent с разрешением — из него создаём MediaProjection. VirtualDisplay с MediaProjection.createVirtualDisplay() даёт Surface, на который система рисует содержимое экрана.
Для передачи через WebRTC используем ScreenCapturerAndroid (из org.webrtc): он оборачивает MediaProjection и поставляет кадры в VideoSource.
С Android 10+ при запуске screen capture через ForegroundService нужен android:foregroundServiceType="mediaProjection" в манифесте. С Android 14 — дополнительно явный ServiceInfo.FOREGROUND_SERVICE_TYPE_MEDIA_PROJECTION при старте сервиса через startForeground(). Без этого — SecurityException при попытке запустить ForegroundService с mediaProjection на Android 14+.
При разрыве соединения MediaProjection.Callback.onStop() вызывается — нужно обработать и уведомить пользователя, а не просто логировать. Пользователь может завершить screen sharing из шторки уведомлений, и приложение должно корректно перейти в режим без демонстрации.
Передача кадров в real-time
И на iOS, и на Android кадры захвата экрана — это CMSampleBuffer (iOS) или Bitmap/Image через ImageReader (Android). Для передачи по сети через WebRTC конвертируем в RTCVideoFrame с YUV420PlanarBuffer (iOS) или JavaI420Buffer (Android). Конвертация YUV из BGRA — дорогая операция, делаем в нативном коде (C++) или через аппаратный конвертер.
Для Agora — AgoraRtcKit.startScreenCapture() принимает конфигурацию с contentHint: .text для оптимизации кодека под текстовый контент (меньше motion, высокая резкость).
Сроки
iOS (ReplayKit + Broadcast Extension + WebRTC) — 3–5 дней при готовом сигналинге. Android (MediaProjection + WebRTC) — 2–3 дня. Обе платформы с корректным lifecycle, фоновым режимом и UX уведомления — 1–1.5 недели. Стоимость рассчитывается индивидуально.







