ExoPlayer
ExoPlayer copied to clipboard
Quick black flickering before starting video
Bug
Hi team, I am working on an app and we are using exoplayer. I am releasing player when fragment destroy and initialising new instance when fragment loads again but still facing a quick black flickering before video loads. Can you guys please help me out with the issue?
Please take a look at below fragment.
Looking forward to hearing from you guys soon.
package de.etherapists.session.lib.presentation.yoga
@AndroidEntryPoint
class YogaSessionFragment(
private val yogaLoopModel: YogaLoopModel? = null,
private val listener: ExerciseListener
) : SessionFragment() {
companion object {
const val START_TIME = "01:00"
const val DELAY = 100L
const val SEEK_VALUE = 15000
fun newInstance(
yogaLoopModel: YogaLoopModel,
listener: ExerciseListener
) = YogaSessionFragment(
yogaLoopModel,
listener
)
}
private var runAnimation = mutableStateOf(true)
private var exoPlayer: ExoPlayer? = null
private var playWhenReady = true
private var currentMediaItem = 0
private var playbackPosition = 0L
private var mediaControlState = mutableStateOf(ControlState.PAUSED)
private var pausedByUser = false
private var hasMediaEnded = false
private var playerStateListener: Player.Listener? = null
private val viewModel: YogaSessionViewModel by viewModels()
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View =
ComposeView(requireContext()).apply {
setContent {
val state by viewModel.state.collectAsState()
state.data?.let { exercise ->
exoPlayer = remember {
viewModel.setupPlayer(
context = requireContext(),
yogaLoopModel = exercise,
playWhenReady = playWhenReady,
currentMediaItem = currentMediaItem,
playbackPosition = playbackPosition,
)
}
exoPlayer?.let { player ->
HDLVideoPlayer(
renderingPlayer = RenderingPlayerExoPlayer
.Builder(videoPlayer = player)
.build()
)
var totalTimeState by remember {
mutableStateOf(START_TIME)
}
var remainingTimeState by remember {
mutableStateOf(START_TIME)
}
var controlBarValue by remember {
mutableStateOf(0f)
}
LaunchedEffect(runAnimation.value) {
while (true) {
remainingTimeState = getTimeFormatted(player.currentPosition + 1)
controlBarValue = (1f * player.currentPosition) / player.duration
delay(DELAY)
}
}
playerStateListener = object : Player.Listener {
override fun onPlaybackStateChanged(playbackState: Int) {
when (playbackState) {
Player.STATE_READY -> {
totalTimeState = getTimeFormatted(player.duration)
hasMediaEnded = false
}
Player.STATE_ENDED -> {
if (!hasMediaEnded) {
hasMediaEnded = true
listener.onYogaExerciseFinished()
}
}
else -> {
}
}
}
}
playerStateListener?.let {
player.removeListener(it)
player.addListener(it)
}
YogaSessionFlowComposable(
state = state,
controlType = ControlType.AUDIO,
currentTime = remainingTimeState,
totalTime = totalTimeState,
totalSteps = exercise.totalExercises ?: 0,
currentStep = exercise.exerciseNumber + 1,
onValueChange = {},
onValueChangeFinished = {},
controlBarValue = controlBarValue,
mediaControlState = mediaControlState,
player = player,
playControl = {
pausedByUser = it
},
skipForwardControl = {
with(player) {
seekTo(currentPosition + SEEK_VALUE)
}
},
skipBackwardControl = {
with(player) {
seekTo(currentPosition - SEEK_VALUE)
}
},
onCloseButtonClick = {
player.pause()
listener.leaveYogaSession(
exercise.id,
exercise.exerciseDuration,
) {
player.play()
}
},
onSoundButtonClick = { mute ->
if (mute) {
player.volume = 0f
} else {
player.volume = 1.0f
}
}
)
}
}
}
}
override fun onStart() {
super.onStart()
setHasOptionsMenu(false)
viewModel.setData(yogaLoopModel)
}
override fun onResume() {
super.onResume()
runAnimation.value = true
}
override fun onPause() {
super.onPause()
runAnimation.value = false
if (!pausedByUser) {
exoPlayer?.pause()
mediaControlState.value = ControlState.PLAYING
}
}
override fun onDestroy() {
super.onDestroy()
systemUI(hide = true)
releasePlayer()
}
fun setData(yogaLoopModel: YogaLoopModel) {
viewModel.setData(yogaLoopModel)
exoPlayer?.stop()
viewModel.setMediaSource(yogaLoopModel)?.let { mediaSource ->
exoPlayer?.setMediaSource(mediaSource)
exoPlayer?.prepare()
}
}
private fun releasePlayer() {
exoPlayer?.let { player ->
playbackPosition = player.currentPosition
currentMediaItem = player.currentMediaItemIndex
playWhenReady = player.playWhenReady
playerStateListener?.let {
player.removeListener(it)
}
player.release()
}
exoPlayer = null
}
}
Could you clarify what you mean by "black flickering" exactly? Is it a black screen until the first frame is shown, or do see a previous frame (from another playback), followed by black and then the correct frame?
When you prepare the player, the first frame won't be available instantly. Usually, this means PlayerView will show a black screen until your video is ready. So this may just be working as expected. You could choose to display a "Loading video..." message or a placeholder image until you receive Player.Listener.onRenderedFirstFrame(). PlayerView also has a built-in functionality for this where you can assign a View to the exo_shutter id and then PlayerView takes care of the timing.
Another aspect is that I'm not sure about the Compose state model here and whether the player is correctly released and removed from the view when needed. This may be the case when you see a frame from the previous playback pop up in the beginning.
Closing due to inactivity