Как реализовать приостановку трансляции и фоновый стриминг на Android с помощью опенсорс-библиотеки

Моя цель - предложение широкого ассортимента товаров и услуг на постоянно высоком качестве обслуживания по самым выгодным ценам.

Если вы решили сделать собственное приложение для стриминга на Android, при разработке нужно учесть множество разных нюансов. Например, зрители могут свернуть вашу трансляцию в процессе просмотра, а через какое-то время вернуться обратно. Как должно при этом работать приложение? Должна ли трансляция приостановиться или идти фоном?

Какое именно поведение реализовать в подобной ситуации — решать вам. Но очень важно предусмотреть подобные моменты технически, чтобы при сворачивании всё работало так, как вы задумали.

Этот материал — продолжение моей предыдущей статьи про создание мобильного приложения для стриминга на Android. В ней я рассказывал о базовых моментах разработки. А сейчас поговорим о нюансах. Расскажу, как технически реализовать приостановку трансляции и фоновый стриминг на Android с помощью опенсорс-библиотеки rtmp-rtsp-stream-client-java.

Фоновый стриминг

Сначала разберём кейс, когда приложение переходит в фон и обратно на передний план. Если заглянуть чуть глубже в исходный код rtmp-rtsp-stream-client-java, станет понятно, что стриминг сам по себе проходит в отдельном потоке:

package com.pedro.rtmp.rtmpclass RtmpClient(private val connectCheckerRtmp: ConnectCheckerRtmp) {       //...       @JvmOverloads       fun connect(url: String?, isRetry: Boolean = false) {            //...            if (!isStreaming || isRetry) {                //...                isStreaming = true                thread = Executors.newSingleThreadExecutor()                thread?.execute post@{                      try {                           if (!establishConnection()) {                               connectCheckerRtmp.onConnectionFailedRtmp("Handshake failed")                                 return@post                           }                           val writer = this.writer ?: throw IOException("Invalid writer, Connection failed")                             commandsManager.sendChunkSize(writer)                             commandsManager.sendConnect("", writer)                           //read packets until you did success connection to server and you are ready to send packets                           while (!Thread.interrupted() && !publishPermitted) {                                   //Handle all command received and send response for it.                                    handleMessages()                           }                           //read packet because maybe server want send you something while streaming                             handleServerPackets()                     } catch (e: Exception) {                          Log.e(TAG, "connection error", e)                            connectCheckerRtmp.onConnectionFailedRtmp("Error configure stream, ${e.message}")                          return@post                      }                }            }      }      //...}

Это очень упрощает  задачу. Получается, нам не нужно пытаться самим вынести этот процесс в отдельный поток.

Но кроме этого нужно учитывать жизненный цикл компонента, в котором у нас инициализируется стриминг, чтобы быть уверенными, что с нашим объектом для стриминга и с самим вещанием ничего не произойдет. Поэтому я решил инициализировать стриминг во ViewModel. Он остается живым на протяжении всех жизненных процессов компонента, к которому привязан (Activity, Fragment).

Жизненный цикл ViewModel
Жизненный цикл ViewModel

Замечу, что это лишь один из способов, и можно использовать и другие: например, Foreground Service. 

В жизненном цикле ViewModel ничего не изменится, даже если произойдет смена конфигурации, ориентации, переход в фон или что-нибудь ещё в этом роде. Но одна проблема всё-таки есть. Для стриминга нужно создать объект RtmpCamera2(). Он зависит от объекта OpenGlView, а это элемент UI, и значит, он уничтожится при переходе приложения в фон. И дальнейшее вещание станет невозможно.

К счастью, в библиотеке предусмотрена возможность заменять на лету View объекта RtmpCamera2. Мы можем заменить её любым объектом нашего приложения, в том числе Context, который живёт, пока сервис не уничтожен системой, или пользователь сам не закрыл его.

В итоге, индикатором перехода приложения в фон будем считать уничтожение объекта OpenGlView. А возврат на передний план, соответственно, создание этого View. Значит, нужно реализовать для этого соответствующий коллбэк:

private val surfaceHolderCallback = object : SurfaceHolder.Callback {      override fun surfaceCreated(holder: SurfaceHolder) {             viewModel.appInForeground(binding.openGlView)      }      override fun surfaceChanged(holder: SurfaceHolder, format: Int, width: Int, height: Int) {}        override fun surfaceDestroyed(holder: SurfaceHolder) {             viewModel.appInBackground()      }} 
binding.openGlView.holder.addCallback(surfaceHolderCallback)
Ну и заменяем OpenGlView на Context. Для этого во ViewModel определим нужные методы:
class StartBroadcastViewModel(application: Application) :AndroidViewModel(application) {         //...        fun appInForeground(openGlView: OpenGlView) {              rtmpCamera2?.let {                     it.replaceView(openGlView)                     it.startPreview(                         StreamParameters.resolution.width,                           StreamParameters.resolution.height                     )              }        }        fun appInBackground() {              rtmpCamera2?.let {                     it.stopPreview()                     it.replaceView(getApplication() as Context)              }        }        //...        override fun onCleared() {               super.onCleared()               rtmpCamera2?.let {                      if (it.isStreaming) {                          _streamState.value = StreamState.STOP                          it.stopStream()                      }                      it.stopPreview()               }        }} 

Также нужно остановить трансляцию при уничтожении ViewModel.

Приостановка трансляции

К сожалению, в библиотеке rtmp-rtsp-stream-client-java не реализована функция приостановки стриминга с сохранением соединения с сервером. Приходится останавливать трансляцию и заново стартовать, а это приводит к лишним задержкам. Чтобы решить эту проблему, я решил имитировать приостановку трансляции отключением камеры и микрофона. Эти функции в библиотеке как раз были доступны.

В этом случае соединение с сервером не обрывается, и задержка при возобновлении трансляции не превышает 8 секунд (стандартная задержка в трансляциях). При этом битрейт при имитации снижается до 70-80 Кбит/с, а значит лишний интернет-трафик практически не расходуется.

//...fun resumeStream() {      rtmpCamera2?.let {             it.enableAudio()             it.glInterface.unMuteVideo()             _streamState.value = StreamState.PLAY      }}fun pauseStream() {      rtmpCamera2?.let {             it.disableAudio()             it.glInterface.muteVideo()             _streamState.value = StreamState.PAUSE       }}//...

Как видите, реализовать и фоновый стриминг, и приостановку довольно просто. И rtmp-rtsp-stream-client-java даёт для этого все возможности.

Источник: https://habr.com/ru/companies/edgecenter/articles/771276/


Интересные статьи

Интересные статьи

В последнее время я публикую заметки, которые демонстрируют работу с пакетом tidymodels . Я разбираю как простые, так и более сложными модели. Сегодняшняя заметка подойдет тем, кто только на...
Давняя проблема стриминговых сервисов связана с тем, что до музыкантов доходит менее цента за одно прослушивание трека. Чтобы заработать больше, некоторые исполнители обращаются к альтернативным спосо...
Напомню, LiteManager — это программа для удаленного доступа к компьютерам, управления рабочим столом, файлами и т.д. Основная версия предназначена для платформы Windows, но в пятой верс...
Кратко объясню, что будет происходить в этой статье: покажу, как использовать PyTorch C++ API для интеграции нейросети в проект на движке Unity; сам проект я подробно описывать ...
Всем привет, меня зовут Артем Жаринов, я специалист по анализу данных и машинному обучению команды RnD в Lamoda. Блуждая по нашему сайту вы, возможно, заметили такие полки рекомендаций...