Скриншаринг (screen sharing — демонстрация экрана при видеозвонке) — уже практически базовая функция платформ для видеозвонков. Skype, WhatsApp, Telegram, Teams, Google Meet — внутри всех этих систем есть эта фича.

Мы уже рассказывали, как реализовать скриншаринг в мобильном iOS-приложении. В этой статье объясним, как сделать это на Android. С примерами кода.

Также в других статьях нашего Android-цикла узнаете, как сделать кастомное уведомление о звонке и в целом, как мы разрабатываем WebRTC-системы для звонков.

Реализация скриншаринга

Скриншаринг можно включить сразу же при создании нового видеозвонка — заранее, фактически до того, как он начался.

Однако мы рассмотрим наиболее часто встречающуюся ситуацию — включение скриншаринга во время видеозвонка — через N секунд после его начала.

Для упрощения описания реализации скриншаринга допустим, что у нас уже есть готовое приложение с WebRTC звонками. Читайте про реализацию видеозвонка WebRTC подробнее.

Шаги реализации:

  1. Получение доступа к содержимому экрана
  2. Создание видеотрека с изображением с экрана
  3. Замена видеотрека с камеры видеотреком с экрана
  4. Отображение уведомления о происходящем скриншаринге

Теперь каждый в деталях:

Получение доступа к содержимому экрана

Сначала получим возможность захватывать содержимое экрана и звук устройства с помощью Media Projection API:

 
 val screenSharingPermissionLauncher = registerForActivityResult(
ActivityResultContracts.StartActivityForResult()
) { result ->
// Handle request result
val screenSharingIntent = result.data
if (screenSharingIntent != null) {
// Success request
}
}
val mediaProjectionManager = getSystemService(Context.MEDIA_PROJECTION_SERVICE) as MediaProjectionManager
val intent = mediaProjectionManager.createScreenCaptureIntent()
screenSharingPermissionLauncher.launch(intent)
 

При вызове screenSharingPermission Launcher.launch(intent) отобразится диалог, который расскажет пользователю о том, что media projection получит доступ ко всей отображаемой на экране информации.

В результате успешного получения доступа к содержимому экрана мы получим screenSharingIntent.

Создание видеотрека с изображением с экрана

Создадим videoCapturer, который будет захватывать изображение с экрана:

 
val mediaProjectionCallback = object : MediaProjection.Callback() {
override fun onStop() {
// screen capture stopped
}
}
val videoCapturer = ScreenCapturerAndroid(screenSharingIntent, mediaProjectionCallback)
 

Далее создадим localVideoTrack:

 
 val surfaceTextureHelper = SurfaceTextureHelper.create("CaptureThread", eglBase.eglBaseContext)

val videoSource = peerConnectionFactory.createVideoSource(/* isScreencast = */ true)

videoCapturer.initialize(surfaceTextureHelper,context, videoSource.capturerObserver)

videoCapturer.startCapture(displayWidth, displayHeight, fps)

val localVideoTrack = peerConnectionFactory.createVideoTrack(VIDEO_TRACK_ID, videoSource)

Замена видеотрека с камеры видеотреком с экрана

Для корректной замены видеотрека реализуем логику renegotiation у обоих участников звонка. При изменении локальных медиа треков WebRTC вызывает метод onRenegotiationNeeded. В нем повторяем процесс обмена sdp:

 
val peerConnectionObserver = object : PeerConnection.Observer {
...
override fun onRenegotiationNeeded() {
// Launch sdp exchange
peerConnection.createOffer(...)
}
}
val peerConnection = peerConnectionFactory.createPeerConnection(iceServers, peerConnectionObserver)
 

Приступим к замене видеотрека. Удалим из локального медиа потока видеотрек с камеры:

 
localMediaStream.removeTrack(cameraVideoTrack)
 

Остановим захват видео с камеры:

 
cameraVideoCapturer.stopCapture()
 

Добавим видеотрек скриншаринга:

 
 localMediaStream.addTrack(screenVideoTrack)
 

Отображение уведомления о происходящем скриншаринге

При старте скриншаринга необходимо запускать Foreground Service c уведомлением о скриншаринге.

Создадим ScreencastService и добавим его в AndroidManifest.xml. Также укажем параметр foregroundServiceType:

 
 service
android:name=".ScreencastService"
android:foregroundServiceType="mediaProjection"
android:stopWithTask="true" /
 

Перед заменой видеотрека с камеры на видеотрек скриншаринга запустим ScreencastService:

 
val intent = Intent(this, ScreencastService::class.java)
ContextCompat.startForegroundService(this, intent)
 

Далее в ScreencastService, например в onStartCommand(), нужно вызвать метод startForeground:

  
startForeground(NOTIFICATION_ID, notification)
 

Частые проблемы во время реализации

Приложение крашится на устройствах с Android 10+ с ошибкой “Media projections require a foreground service of type ServiceInfo.FOREGROUND_SERVICE_TYPE_MEDIA_PROJECTION”

Эта ошибка возникает, когда скриншаринг запускается без Foreground Service. Начиная с Android 10 система требует, чтобы во время скриншаринга был запущен Foreground Service c “android:foregroundServiceType="mediaProjection”.

Foreground Service гарантирует, что система не “убьет” приложение во время скриншаринга. Уведомление Foreground Service будет сообщать пользователю о текущем скриншаринге и даст возможность быстро вернуться в приложение.

Как решить: не забыть отобразить уведомление о начатом скриншаринге :)

Не происходит замена видеотрека с камеры видеотреком с экрана

Проблема замены видеотрека может быть связана с тем, что на стороне одного (или обоих) участников видеозвонка не реализована (или реализована некорректно) логика renegotiation.

Как решить: переопределить метод onRenegotiationNeeded в PeerConnection.Observer (название метода на других платформах может отличаться). При вызове onRenegotiationNeeded должен запускаться процесс обмена sdp.

Заключение

В этой статье мы рассмотрели реализацию скриншаринга в видеозвонке:

  • Получили доступ к содержимому экрана с помощью MediaProjection API
  • Захватили содержимое экрана с помощью ScreenCapturerAndroid
  • Создали локальный видеотрек с изображением с экрана
  • Заменили видеотрек с камеры на видеотрек скриншаринга
  • Реализовали Foreground Service для отображения уведомления о скриншаринге

Посмотреть на готовую реализацию скриншаринга можно в нашем приложении.

  • Разработка