From 96c380d6ec74297d3f63faf633d5e194b3f34f97 Mon Sep 17 00:00:00 2001 From: Brian Wernick Date: Sun, 15 Dec 2024 15:19:02 -0700 Subject: [PATCH] Issue-810: Fixed an issue causing removed control components to be re-added on load state changes --- .../widget/controls/DefaultVideoControls.kt | 67 +++++++++++++++++-- .../ui/widget/controls/VideoControlsMobile.kt | 10 +-- .../ui/widget/controls/VideoControlsTv.kt | 14 ++-- 3 files changed, 72 insertions(+), 19 deletions(-) diff --git a/library/src/main/kotlin/com/devbrackets/android/exomedia/ui/widget/controls/DefaultVideoControls.kt b/library/src/main/kotlin/com/devbrackets/android/exomedia/ui/widget/controls/DefaultVideoControls.kt index 4753d988..03048a83 100644 --- a/library/src/main/kotlin/com/devbrackets/android/exomedia/ui/widget/controls/DefaultVideoControls.kt +++ b/library/src/main/kotlin/com/devbrackets/android/exomedia/ui/widget/controls/DefaultVideoControls.kt @@ -5,7 +5,6 @@ import android.graphics.drawable.Drawable import android.os.Handler import android.os.Looper import android.util.AttributeSet -import android.util.SparseBooleanArray import android.view.View import android.widget.ImageButton import android.widget.ProgressBar @@ -13,6 +12,7 @@ import android.widget.RelativeLayout import android.widget.SeekBar import android.widget.TextView import androidx.annotation.ColorRes +import androidx.annotation.IdRes import androidx.annotation.IntRange import androidx.annotation.LayoutRes import androidx.media3.common.Timeline @@ -67,8 +67,7 @@ abstract class DefaultVideoControls : RelativeLayout, VideoControls, OnTimelineC var visibilityListener: VideoControlsVisibilityListener? = null protected var internalListener = InternalListener() - - protected var enabledViews = SparseBooleanArray() + protected val configuration = Configuration() /** * The delay in milliseconds to wait to start the hide animation @@ -179,7 +178,7 @@ abstract class DefaultVideoControls : RelativeLayout, VideoControls, OnTimelineC timeline.getWindow(timeline.windowCount -1, window) if (window.isPlaceholder) { return onTimelineStyleUpdated(TimelineStyle.UNKNOWN) - } else if (!window.isLive()) { + } else if (!window.isLive) { return onTimelineStyleUpdated(TimelineStyle.ON_DEMAND) } @@ -190,7 +189,7 @@ abstract class DefaultVideoControls : RelativeLayout, VideoControls, OnTimelineC return onTimelineStyleUpdated(TimelineStyle.UNKNOWN) } - val rollingStart = window.isDynamic || window.isLive() + val rollingStart = window.isDynamic || window.isLive val style = when { rollingStart -> TimelineStyle.LIVE else -> TimelineStyle.EVENT @@ -419,7 +418,7 @@ abstract class DefaultVideoControls : RelativeLayout, VideoControls, OnTimelineC */ fun setPreviousButtonEnabled(enabled: Boolean) { previousButton.isEnabled = enabled - enabledViews.put(R.id.exomedia_controls_previous_btn, enabled) + configuration.setEnabled(R.id.exomedia_controls_previous_btn, enabled) } /** @@ -430,7 +429,7 @@ abstract class DefaultVideoControls : RelativeLayout, VideoControls, OnTimelineC */ fun setNextButtonEnabled(enabled: Boolean) { nextButton.isEnabled = enabled - enabledViews.put(R.id.exomedia_controls_next_btn, enabled) + configuration.setEnabled(R.id.exomedia_controls_next_btn, enabled) } /** @@ -471,6 +470,7 @@ abstract class DefaultVideoControls : RelativeLayout, VideoControls, OnTimelineC */ fun setPreviousButtonRemoved(removed: Boolean) { previousButton.visibility = if (removed) View.GONE else View.VISIBLE + configuration.setRemoved(R.id.exomedia_controls_previous_btn, removed) } /** @@ -481,6 +481,7 @@ abstract class DefaultVideoControls : RelativeLayout, VideoControls, OnTimelineC */ fun setNextButtonRemoved(removed: Boolean) { nextButton.visibility = if (removed) View.GONE else View.VISIBLE + configuration.setRemoved(R.id.exomedia_controls_next_btn, removed) } /** @@ -739,6 +740,58 @@ abstract class DefaultVideoControls : RelativeLayout, VideoControls, OnTimelineC } } + // TODO: rename to something a bit better... + protected class Configuration { + private val itemConfigs = mutableMapOf() + + fun isEnabled(@IdRes id: Int): Boolean { + return getViewConfiguration(id)?.enabled ?: true + } + + fun setEnabled(@IdRes id: Int, enabled: Boolean) { + updateViewConfiguration(id) { + it.copy(enabled = enabled) + } + } + + fun isRemoved(@IdRes id: Int): Boolean { + return getViewConfiguration(id)?.removed ?: false + } + + fun setRemoved(@IdRes id: Int, removed: Boolean) { + updateViewConfiguration(id) { + it.copy(removed = removed) + } + } + + /** + * Helper function to get the Visibility value for the view with [id] + */ + fun visibility(@IdRes id: Int): Int { + return if (isRemoved(id)) View.GONE else View.VISIBLE + } + + private fun getViewConfiguration(@IdRes id: Int): ItemConfig? { + return synchronized(this) { + itemConfigs[id] + } + } + + private fun updateViewConfiguration(@IdRes id: Int, action: (ItemConfig) -> ItemConfig?) { + synchronized(this) { + when (val newConfig = action(itemConfigs[id] ?: ItemConfig())) { + null -> itemConfigs.remove(id) + else -> itemConfigs[id] = newConfig + } + } + } + + private data class ItemConfig( + val enabled: Boolean = true, + val removed: Boolean = false + ) + } + enum class LoadState { /** * Occurs when the media content is being prepared for playback. This diff --git a/library/src/main/kotlin/com/devbrackets/android/exomedia/ui/widget/controls/VideoControlsMobile.kt b/library/src/main/kotlin/com/devbrackets/android/exomedia/ui/widget/controls/VideoControlsMobile.kt index b5467444..595728ab 100644 --- a/library/src/main/kotlin/com/devbrackets/android/exomedia/ui/widget/controls/VideoControlsMobile.kt +++ b/library/src/main/kotlin/com/devbrackets/android/exomedia/ui/widget/controls/VideoControlsMobile.kt @@ -10,7 +10,7 @@ import android.view.ViewGroup import android.widget.LinearLayout import android.widget.SeekBar import com.devbrackets.android.exomedia.R -import java.util.* +import java.util.LinkedList /** * Provides playback controls for the [com.devbrackets.android.exomedia.ui.widget.VideoView] @@ -130,11 +130,11 @@ class VideoControlsMobile : DefaultVideoControls { playPauseButton.visibility = View.VISIBLE playPauseButton.isEnabled = true - previousButton.visibility = View.VISIBLE - previousButton.isEnabled = enabledViews.get(R.id.exomedia_controls_previous_btn, true) + previousButton.visibility = configuration.visibility(R.id.exomedia_controls_previous_btn) + previousButton.isEnabled = configuration.isEnabled(R.id.exomedia_controls_previous_btn) - nextButton.visibility = View.VISIBLE - nextButton.isEnabled = enabledViews.get(R.id.exomedia_controls_next_btn, true) + nextButton.visibility = configuration.visibility(R.id.exomedia_controls_next_btn) + nextButton.isEnabled = configuration.isEnabled(R.id.exomedia_controls_next_btn) extraViewsContainer.visibility = View.VISIBLE diff --git a/library/src/main/kotlin/com/devbrackets/android/exomedia/ui/widget/controls/VideoControlsTv.kt b/library/src/main/kotlin/com/devbrackets/android/exomedia/ui/widget/controls/VideoControlsTv.kt index cb3cbd0e..352c2e36 100644 --- a/library/src/main/kotlin/com/devbrackets/android/exomedia/ui/widget/controls/VideoControlsTv.kt +++ b/library/src/main/kotlin/com/devbrackets/android/exomedia/ui/widget/controls/VideoControlsTv.kt @@ -5,13 +5,14 @@ import android.util.AttributeSet import android.view.KeyEvent import android.view.View import android.view.ViewGroup -import android.widget.* +import android.widget.LinearLayout +import android.widget.SeekBar import androidx.core.view.forEach import com.devbrackets.android.exomedia.BuildConfig import com.devbrackets.android.exomedia.R import com.devbrackets.android.exomedia.util.view.DelegatedOnKeyListener import com.devbrackets.android.exomedia.util.view.UnhandledMediaKeyLogger -import java.util.* +import java.util.LinkedList /** * Provides playback controls for the [com.devbrackets.android.exomedia.ui.widget.VideoView] @@ -158,11 +159,11 @@ class VideoControlsTv : DefaultVideoControls { playPauseButton.visibility = View.VISIBLE playPauseButton.isEnabled = true - previousButton.visibility = View.VISIBLE - previousButton.isEnabled = enabledViews.get(R.id.exomedia_controls_previous_btn, true) + previousButton.visibility = configuration.visibility(R.id.exomedia_controls_previous_btn) + previousButton.isEnabled = configuration.isEnabled(R.id.exomedia_controls_previous_btn) - nextButton.visibility = View.VISIBLE - nextButton.isEnabled = enabledViews.get(R.id.exomedia_controls_next_btn, true) + nextButton.visibility = configuration.visibility(R.id.exomedia_controls_next_btn) + nextButton.isEnabled = configuration.isEnabled(R.id.exomedia_controls_next_btn) extraViewsContainer.visibility = View.VISIBLE @@ -437,7 +438,6 @@ class VideoControlsTv : DefaultVideoControls { return seekBy(-FAST_FORWARD_REWIND_AMOUNT) } - @Suppress("FoldInitializerAndIfToElvis") private fun seekBy(amountMillis: Long): Boolean { val view = videoView if (view == null || !view.isAttachedToWindow) {