Реализация Screen Broadcasting (трансляция экрана) с мобильного устройства
Трансляция экрана на iOS работает через ReplayKit — и только через него. Попытки захватить UIScreen напрямую через UIScreen.main.snapshotView дают UIImage один раз, а не поток кадров. ReplayKit — единственный публичный API Apple для screen capture в реальном времени.
Два режима ReplayKit и когда что использовать
RPScreenRecorder (in-app recording). Захватывает только контент внутри приложения. Пользователь не видит системный picker. Подходит для записи геймплея, capture UI приложения. Недостаток — захват прекращается при сворачивании.
RPBroadcastSampleHandler (broadcast upload extension). Работает как системный Extension — процесс живёт отдельно от основного приложения. Захватывает весь экран устройства, включая другие приложения и уведомления. Пользователь запускает через Control Center или RPSystemBroadcastPickerView. Именно этот режим нужен для трансляции «всего экрана».
Архитектура Broadcast Extension:
iOS System → RPBroadcastSampleHandler (Extension process)
↓
CMSampleBuffer (video + audio)
↓
App Group shared container (если нужна коммуникация с основным приложением)
↓
RTMP/SRT → сервер трансляции
Расширение не имеет UI и ограничено 50 МБ RAM. Это жёсткое ограничение: всё кодирование и отправка должны умещаться в этот бюджет.
RPBroadcastSampleHandler: реализация
class BroadcastHandler: RPBroadcastSampleHandler {
private var rtmpStream: RTMPStream?
override func broadcastStarted(withSetupInfo setupInfo: [String: NSObject]?) {
// Инициализируем RTMP или SRT соединение
// setupInfo — данные от основного приложения через Info.plist
}
override func processSampleBuffer(_ sampleBuffer: CMSampleBuffer,
with sampleBufferType: RPSampleBufferType) {
switch sampleBufferType {
case .video:
rtmpStream?.append(sampleBuffer)
case .audioApp:
rtmpStream?.append(sampleBuffer) // аудио приложений
case .audioMic:
// аудио микрофона — отдельный поток, требует явного разрешения
break
}
}
override func broadcastFinished() {
rtmpStream?.close()
}
}
Передача параметров (stream key, endpoint) из основного приложения в Extension — через App Group UserDefaults:
let defaults = UserDefaults(suiteName: "group.com.yourapp.broadcast")
defaults?.set(streamKey, forKey: "streamKey")
Задержка и пропускная способность
ReplayKit screen broadcast добавляет буферизацию ~2–5 секунд. Это не баг — Apple буферизирует для защиты конфиденциальности (показ системных диалогов с паролями). Снизить нельзя.
Разрешение зависит от модели устройства: iPhone 13+ отдаёт 1668×2388 (native scale), что избыточно для стрима. В processSampleBuffer перед энкодированием нужно downscale через VTPixelTransferSession или CIContext:
// Масштабируем до 1280×720 перед отправкой в энкодер
let scaledBuffer = pixelTransferSession.scale(pixelBuffer, to: CGSize(width: 1280, height: 720))
Без downscale Extension превысит 50 МБ RAM на первом же I-frame в 4K.
Android: MediaProjection API
На Android аналог — MediaProjection. Пользователь подтверждает разрешение через системный диалог (startActivityForResult с MediaProjectionManager.createScreenCaptureIntent()). После получения MediaProjection создаём VirtualDisplay и направляем его в MediaCodec через Surface:
val virtualDisplay = mediaProjection.createVirtualDisplay(
"ScreenCapture",
width, height, dpi,
DisplayManager.VIRTUAL_DISPLAY_FLAG_AUTO_MIRROR,
mediaCodec.createInputSurface(), null, null
)
В отличие от iOS, задержки ReplayKit нет — захват идёт почти в реальном времени. Но на Android 14+ появилось требование: если MediaProjection-сессия используется в ForegroundService, нужен тип FOREGROUND_SERVICE_TYPE_MEDIA_PROJECTION.
Уведомление пользователя и сторожевой таймер
На iOS Extension может работать бесконечно, но если процесс превышает 50 МБ — система его убивает без предупреждения. Для мониторинга из основного приложения — heartbeat через App Group: Extension пишет timestamp каждые 5 секунд, приложение проверяет. Если timestamp не обновлялся 15 секунд — считаем Extension упавшим и показываем предупреждение.
Сроки
iOS Broadcast Extension с RTMP/SRT, downscaling, App Group коммуникацией: 2–3 недели. Android MediaProjection: 1.5–2 недели. Кросс-платформа с общим управляющим кодом: 4–5 недель. Стоимость рассчитывается индивидуально.







