Как стать автором
Обновить
55.16

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

Уровень сложностиСредний
Время на прочтение4 мин
Количество просмотров963

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

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

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

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

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

package com.pedro.rtmp.rtmp
class 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).

Замечу, что это лишь один из способов, и можно использовать и другие: например, 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 даёт для этого все возможности.

Теги:
Хабы:
Всего голосов 2: ↑1 и ↓10
Комментарии0

Публикации

Информация

Сайт
edgecenter.ru
Дата регистрации
Дата основания
Численность
201–500 человек
Местоположение
Россия