mirror of
https://github.com/zoriya/react-native-video.git
synced 2026-06-07 20:31:58 +00:00
feat(android): optimizes video controls for small video players (#17)
Co-authored-by: Pieczasz <bartekp854@gmail.com> Co-authored-by: Krzysztof Moch <krzysmoch.programs@gmail.com>
This commit is contained in:
committed by
GitHub
parent
146471d23c
commit
6baa1e4f4a
+8
@@ -1,6 +1,7 @@
|
||||
package com.video.core.fragments
|
||||
|
||||
import android.annotation.SuppressLint
|
||||
import android.content.Context
|
||||
import android.content.res.Configuration
|
||||
import android.os.Build
|
||||
import android.os.Bundle
|
||||
@@ -9,6 +10,7 @@ import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import android.view.WindowInsets
|
||||
import android.view.WindowInsetsController
|
||||
import android.view.WindowManager
|
||||
import android.widget.FrameLayout
|
||||
import android.widget.ImageButton
|
||||
import androidx.activity.OnBackPressedCallback
|
||||
@@ -17,6 +19,7 @@ import androidx.core.view.isVisible
|
||||
import androidx.fragment.app.Fragment
|
||||
import androidx.media3.common.util.UnstableApi
|
||||
import com.video.core.utils.PictureInPictureUtils.createPictureInPictureParams
|
||||
import com.video.core.utils.SmallVideoPlayerOptimizer
|
||||
import com.video.view.VideoView
|
||||
import java.util.UUID
|
||||
|
||||
@@ -131,6 +134,9 @@ class FullscreenVideoFragment(private val videoView: VideoView) : Fragment() {
|
||||
|
||||
setupFullscreenButton()
|
||||
videoView.playerView.setShowSubtitleButton(true)
|
||||
|
||||
// Apply optimizations based on video player size in fullscreen mode
|
||||
SmallVideoPlayerOptimizer.applyOptimizations(videoView.playerView, requireContext(), isFullscreen = true)
|
||||
}
|
||||
|
||||
@SuppressLint("PrivateResource")
|
||||
@@ -220,6 +226,8 @@ class FullscreenVideoFragment(private val videoView: VideoView) : Fragment() {
|
||||
videoView.isInFullscreen = false
|
||||
}
|
||||
|
||||
|
||||
|
||||
override fun onDestroy() {
|
||||
super.onDestroy()
|
||||
|
||||
|
||||
+157
@@ -0,0 +1,157 @@
|
||||
package com.video.core.utils
|
||||
|
||||
import android.content.Context
|
||||
import android.util.Log
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import android.widget.ImageButton
|
||||
import androidx.media3.ui.PlayerView
|
||||
|
||||
object SmallVideoPlayerOptimizer {
|
||||
|
||||
fun isSmallVideoPlayer(playerView: PlayerView): Boolean {
|
||||
// Check if the PlayerView dimensions are small enough to warrant optimizations
|
||||
val width = playerView.width
|
||||
val height = playerView.height
|
||||
|
||||
// If view hasn't been measured yet, use layout params or return false
|
||||
if (width <= 0 || height <= 0) {
|
||||
val layoutParams = playerView.layoutParams
|
||||
if (layoutParams != null) {
|
||||
// Convert any specific dimensions to pixels if needed
|
||||
val widthPx = if (layoutParams.width > 0) layoutParams.width else playerView.measuredWidth
|
||||
val heightPx = if (layoutParams.height > 0) layoutParams.height else playerView.measuredHeight
|
||||
|
||||
if (widthPx <= 0 || heightPx <= 0) return false
|
||||
|
||||
return isSmallDimensions(widthPx, heightPx, playerView.context)
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
return isSmallDimensions(width, height, playerView.context)
|
||||
}
|
||||
|
||||
private fun isSmallDimensions(widthPx: Int, heightPx: Int, context: Context): Boolean {
|
||||
val density = context.resources.displayMetrics.density
|
||||
val widthDp = widthPx / density
|
||||
val heightDp = heightPx / density
|
||||
|
||||
// Consider the video player "small" if width <= 400dp or height <= 300dp
|
||||
// These thresholds are more appropriate for actual video player sizes
|
||||
return widthDp <= 400 || heightDp <= 300
|
||||
}
|
||||
|
||||
fun applyOptimizations(
|
||||
playerView: PlayerView,
|
||||
context: Context,
|
||||
isFullscreen: Boolean = false
|
||||
) {
|
||||
playerView.post {
|
||||
try {
|
||||
if (isFullscreen) {
|
||||
// For fullscreen mode, use system defaults - no custom optimizations
|
||||
// Let ExoPlayer use its default controller timeout and styling
|
||||
return@post
|
||||
}
|
||||
|
||||
// Only apply optimizations if the video player itself is small
|
||||
if (!isSmallVideoPlayer(playerView)) {
|
||||
return@post
|
||||
}
|
||||
|
||||
val controllerView = playerView.findViewById<ViewGroup>(androidx.media3.ui.R.id.exo_controller)
|
||||
controllerView?.let { controller ->
|
||||
optimizeControlElementsForSmallPlayer(controller, context)
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
Log.w("ReactNativeVideo", "Error applying small video player optimizations: ${e.message}")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun optimizeControlElementsForSmallPlayer(
|
||||
controller: ViewGroup,
|
||||
context: Context
|
||||
) {
|
||||
val density = context.resources.displayMetrics.density
|
||||
val primaryButtonSize = (48 * density).toInt()
|
||||
val secondaryButtonSize = (44 * density).toInt()
|
||||
|
||||
optimizeButtons(controller, primaryButtonSize, secondaryButtonSize)
|
||||
optimizeProgressBar(controller, context)
|
||||
optimizeTextElements(controller)
|
||||
}
|
||||
|
||||
private fun optimizeButtons(
|
||||
container: ViewGroup,
|
||||
primarySize: Int,
|
||||
secondarySize: Int
|
||||
) {
|
||||
for (i in 0 until container.childCount) {
|
||||
val child = container.getChildAt(i)
|
||||
when (child) {
|
||||
is ImageButton -> {
|
||||
val buttonSize = when (child.id) {
|
||||
androidx.media3.ui.R.id.exo_play_pause -> primarySize
|
||||
androidx.media3.ui.R.id.exo_fullscreen -> primarySize
|
||||
androidx.media3.ui.R.id.exo_settings -> primarySize
|
||||
androidx.media3.ui.R.id.exo_rew -> secondarySize
|
||||
androidx.media3.ui.R.id.exo_ffwd -> secondarySize
|
||||
androidx.media3.ui.R.id.exo_subtitle -> secondarySize
|
||||
androidx.media3.ui.R.id.exo_prev -> secondarySize
|
||||
androidx.media3.ui.R.id.exo_next -> secondarySize
|
||||
else -> secondarySize
|
||||
}
|
||||
|
||||
val params = child.layoutParams
|
||||
params.width = buttonSize
|
||||
params.height = buttonSize
|
||||
child.layoutParams = params
|
||||
|
||||
// Hide less essential buttons on small video players
|
||||
when (child.id) {
|
||||
androidx.media3.ui.R.id.exo_shuffle,
|
||||
androidx.media3.ui.R.id.exo_repeat_toggle,
|
||||
androidx.media3.ui.R.id.exo_vr -> {
|
||||
child.visibility = View.GONE
|
||||
}
|
||||
}
|
||||
}
|
||||
is ViewGroup -> {
|
||||
optimizeButtons(child, primarySize, secondarySize)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun optimizeProgressBar(
|
||||
controller: ViewGroup,
|
||||
context: Context
|
||||
) {
|
||||
val progressContainer = controller.findViewById<View>(androidx.media3.ui.R.id.exo_progress)
|
||||
progressContainer?.let { progress ->
|
||||
val params = progress.layoutParams as? ViewGroup.MarginLayoutParams
|
||||
params?.let {
|
||||
it.height = (4 * context.resources.displayMetrics.density).toInt()
|
||||
progress.layoutParams = it
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun optimizeTextElements(
|
||||
controller: ViewGroup
|
||||
) {
|
||||
val timeContainer = controller.findViewById<ViewGroup>(androidx.media3.ui.R.id.exo_time)
|
||||
timeContainer?.let { time ->
|
||||
val positionView = time.findViewById<View>(androidx.media3.ui.R.id.exo_position)
|
||||
val durationView = time.findViewById<View>(androidx.media3.ui.R.id.exo_duration)
|
||||
|
||||
listOf(positionView, durationView).forEach { textView ->
|
||||
if (textView is android.widget.TextView) {
|
||||
textView.textSize = 12f
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -6,9 +6,11 @@ import android.content.Context
|
||||
import android.graphics.Color
|
||||
import android.os.Build
|
||||
import android.util.AttributeSet
|
||||
import android.util.DisplayMetrics
|
||||
import android.util.Log
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import android.view.WindowManager
|
||||
import android.widget.FrameLayout
|
||||
import android.widget.ImageButton
|
||||
import androidx.annotation.RequiresApi
|
||||
@@ -32,6 +34,7 @@ import com.video.core.utils.Threading.runOnMainThread
|
||||
import com.video.core.extensions.toAspectRatioFrameLayout
|
||||
import com.video.core.utils.PictureInPictureUtils
|
||||
import com.video.core.utils.PictureInPictureUtils.createDisabledPictureInPictureParams
|
||||
import com.video.core.utils.SmallVideoPlayerOptimizer
|
||||
|
||||
@UnstableApi
|
||||
class VideoView @JvmOverloads constructor(
|
||||
@@ -109,6 +112,9 @@ class VideoView @JvmOverloads constructor(
|
||||
setShutterBackgroundColor(Color.TRANSPARENT)
|
||||
setShowSubtitleButton(true)
|
||||
useController = false
|
||||
|
||||
// Apply optimizations based on video player size if needed
|
||||
configureForSmallPlayer()
|
||||
}
|
||||
var isInFullscreen: Boolean = false
|
||||
set(value) {
|
||||
@@ -145,6 +151,9 @@ class VideoView @JvmOverloads constructor(
|
||||
MeasureSpec.makeMeasureSpec(height, MeasureSpec.EXACTLY)
|
||||
)
|
||||
layout(left, top, right, bottom)
|
||||
|
||||
// Additional layout fixes for small video players
|
||||
applySmallPlayerLayoutFixes()
|
||||
}
|
||||
|
||||
override fun requestLayout() {
|
||||
@@ -363,4 +372,17 @@ class VideoView @JvmOverloads constructor(
|
||||
setupPipHelper()
|
||||
super.onAttachedToWindow()
|
||||
}
|
||||
|
||||
private fun PlayerView.configureForSmallPlayer() {
|
||||
SmallVideoPlayerOptimizer.applyOptimizations(this, context, isFullscreen = false)
|
||||
|
||||
// Also apply after any layout changes
|
||||
viewTreeObserver.addOnGlobalLayoutListener {
|
||||
SmallVideoPlayerOptimizer.applyOptimizations(this, context, isFullscreen = false)
|
||||
}
|
||||
}
|
||||
|
||||
private fun applySmallPlayerLayoutFixes() {
|
||||
SmallVideoPlayerOptimizer.applyOptimizations(playerView, context, isFullscreen = false)
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user