fixed black screen and save player state after detach in video-custom

This commit is contained in:
edubinskaya
2023-05-22 11:11:50 +03:00
parent 5e4b9b5c47
commit 48b54bf9f7
3 changed files with 77 additions and 11 deletions
@@ -29,6 +29,11 @@ class VideoCustomViewController(
viewModel.configureVideo()
}
internal fun unbind(viewModel: VideoViewModel) {
viewModels.remove(viewModel)
viewModel.release()
}
internal fun release(view: VideoView) {
val viewModel = viewModels.find { it == view.viewModel }
if (viewModel != null) {
@@ -94,6 +99,7 @@ class VideoCustomViewController(
config,
cache,
actionNotifier,
this@VideoCustomViewController,
context
).also { viewModels.add(it) }
}
@@ -1,8 +1,11 @@
package com.yandex.div.video.custom
import android.app.Activity
import android.app.Application
import android.content.Context
import android.graphics.Color
import android.graphics.PixelFormat
import android.os.Bundle
import android.view.SurfaceView
import android.view.ViewGroup.LayoutParams.MATCH_PARENT
import android.widget.FrameLayout
@@ -11,6 +14,9 @@ import androidx.core.view.isVisible
import com.google.android.exoplayer2.ExoPlaybackException
import com.google.android.exoplayer2.Player
import com.google.android.exoplayer2.Player.DISCONTINUITY_REASON_AUTO_TRANSITION
import com.google.android.exoplayer2.Player.STATE_ENDED
import com.google.android.exoplayer2.Player.STATE_READY
import com.google.android.exoplayer2.SeekParameters
import com.google.android.exoplayer2.ui.PlayerView
import com.yandex.div.internal.KAssert
import kotlinx.coroutines.CoroutineScope
@@ -20,6 +26,7 @@ import kotlinx.coroutines.MainScope
import kotlinx.coroutines.cancel
import kotlinx.coroutines.cancelChildren
import kotlinx.coroutines.launch
import kotlin.math.max
internal class VideoView(
context: Context,
@@ -51,9 +58,36 @@ internal class VideoView(
setBackgroundColor(Color.TRANSPARENT)
}
private val playerActivityCallback = object : Application.ActivityLifecycleCallbacks {
override fun onActivityStarted(activity: Activity) = Unit
override fun onActivityDestroyed(activity: Activity) =
(context.applicationContext as Application).unregisterActivityLifecycleCallbacks(this)
override fun onActivitySaveInstanceState(activity: Activity, outState: Bundle) = Unit
override fun onActivityStopped(activity: Activity) = Unit
override fun onActivityCreated(activity: Activity, savedInstanceState: Bundle?) = Unit
override fun onActivityPaused(activity: Activity) {
viewModel?.freezePlayback()
}
override fun onActivityResumed(activity: Activity) {
//This code used to avoid black player after return from background
val player = viewModel?.player ?: return
val state = player.playbackState
viewModel?.unfreezePlayback()
if (state == STATE_ENDED) {
// play video, because we moved not on the last frame.
player.setSeekParameters(SeekParameters.DEFAULT)
player.seekTo(max(player.currentPosition - 1, 0L))
player.setSeekParameters(SeekParameters.EXACT)
viewModel?.player?.play()
}
}
}
init {
addView(stubImageView)
addView(playerView)
}
private val playerListener = object : Player.Listener {
@@ -78,13 +112,23 @@ internal class VideoView(
assertBoundToViewModel()
viewModel?.onPlaybackError(error)
}
override fun onPlaybackStateChanged(state: Int) {
super.onPlaybackStateChanged(state)
when (state) {
STATE_READY -> if (playerView.parent == null) {
stubImageView.setImageBitmap(null)
stubImageView.isVisible = false
addView(playerView)
}
else -> return
}
}
}
fun bindToViewModel(model: VideoViewModel) {
viewModel?.let { pauseObservingViewModel(it) }
stubImageView.setImageBitmap(null)
stubImageView.isVisible = false
viewModel?.let { stopObservingViewModel(it) }
viewModel = model
if (isAttachedToWindow) {
@@ -114,10 +158,10 @@ internal class VideoView(
stubViewObservationJob = observeStubViewState(model)
}
private fun pauseObservingViewModel(model: VideoViewModel) {
playerView.player = null
private fun stopObservingViewModel(model: VideoViewModel) {
model.player.removeListener(playerListener)
model.freezePlayback()
playerView.player = null
model.onDetached()
coroutineScope.coroutineContext.cancelChildren()
stubViewObservationJob = null
}
@@ -126,17 +170,23 @@ internal class VideoView(
super.onAttachedToWindow()
assertBoundToViewModel()
viewModel?.let { resumeObservingViewModel(it) }
(context.applicationContext as Application)
.registerActivityLifecycleCallbacks(playerActivityCallback)
}
override fun onDetachedFromWindow() {
super.onDetachedFromWindow()
viewModel?.let { pauseObservingViewModel(it) }
viewModel?.let { stopObservingViewModel(it) }
(context.applicationContext as Application)
.unregisterActivityLifecycleCallbacks(playerActivityCallback)
}
fun release() {
viewModel?.let { pauseObservingViewModel(it) }
viewModel?.let { stopObservingViewModel(it) }
coroutineScope.cancel()
playerView.player = null
viewModel = null
(context.applicationContext as Application)
.unregisterActivityLifecycleCallbacks(playerActivityCallback)
}
}
@@ -26,6 +26,10 @@ internal interface VideoViewModel {
fun onVideoRepeat()
fun onDetached()
fun release()
fun freezePlayback()
fun unfreezePlayback()
@@ -35,6 +39,7 @@ internal class MutableVideoViewModel(
private val videoConfig: VideoConfig,
private val cache: VideoCache,
private val actionNotifier: VideoCustomActionNotifier,
private val viewController: VideoCustomViewController,
context: Context,
) : VideoViewModel {
private val viewModelScope = CoroutineScope(Dispatchers.Default)
@@ -118,7 +123,12 @@ internal class MutableVideoViewModel(
savedIsPlaying = null
}
fun release() {
override fun onDetached() {
player.stop()
viewController.unbind(this)
}
override fun release() {
videoConfig.id?.let { id ->
if (!isVideoShown) {
actionNotifier.notifyPlaybackFinished(id, player.currentPosition)