mirror of
https://github.com/zoriya/react-native-video.git
synced 2026-05-25 23:58:18 +00:00
refactor(android): use fragment instead of activity for fullscreen view
This commit is contained in:
@@ -1,10 +1,3 @@
|
||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<uses-permission android:name="android.permission.INTERNET" />
|
||||
|
||||
<application>
|
||||
<activity android:name="com.video.core.activities.FullscreenVideoViewActivity"
|
||||
android:supportsPictureInPicture="true"
|
||||
android:configChanges="screenSize|smallestScreenSize|screenLayout|orientation|keyboardHidden"
|
||||
/>
|
||||
</application>
|
||||
</manifest>
|
||||
|
||||
@@ -1,10 +1,3 @@
|
||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<uses-permission android:name="android.permission.INTERNET" />
|
||||
|
||||
<application>
|
||||
<activity android:name="com.video.core.activities.FullscreenVideoViewActivity"
|
||||
android:supportsPictureInPicture="true"
|
||||
android:configChanges="screenSize|smallestScreenSize|screenLayout|orientation|keyboardHidden"
|
||||
/>
|
||||
</application>
|
||||
</manifest>
|
||||
|
||||
@@ -3,7 +3,6 @@ package com.video.core
|
||||
import androidx.annotation.OptIn
|
||||
import androidx.media3.common.util.UnstableApi
|
||||
import com.margelo.nitro.video.HybridVideoPlayer
|
||||
import com.video.core.activities.FullscreenVideoViewActivity
|
||||
import com.video.view.VideoView
|
||||
import java.lang.ref.WeakReference
|
||||
|
||||
@@ -13,16 +12,8 @@ object VideoManager {
|
||||
private val views = mutableMapOf<Int, WeakReference<VideoView>>()
|
||||
// player -> list of nitroIds of views that are using this player
|
||||
private val players = mutableMapOf<HybridVideoPlayer, MutableList<Int>>()
|
||||
// fullscreen activity id (hash code) -> weak FullscreenVideoViewActivity
|
||||
private val fullscreenActivities = mutableMapOf<Int, WeakReference<FullscreenVideoViewActivity>>()
|
||||
|
||||
fun maybePassPlayerToView(player: HybridVideoPlayer) {
|
||||
// If we have fullscreen activity open, we don't want to move player from it
|
||||
// Fullscreen activity will attach player to view after destroy
|
||||
if (fullscreenActivities.isNotEmpty()) {
|
||||
return
|
||||
}
|
||||
|
||||
val views = players[player]?.mapNotNull { getVideoViewWeakReferenceByNitroId(it)?.get() } ?: return
|
||||
val latestView = views.lastOrNull() ?: return
|
||||
|
||||
@@ -52,72 +43,54 @@ object VideoManager {
|
||||
players[player]?.add(view.nitroId)
|
||||
}
|
||||
|
||||
fun removeViewFromPlayer(view: VideoView, player: HybridVideoPlayer, moveToLatestView: Boolean = true) {
|
||||
fun removeViewFromPlayer(view: VideoView, player: HybridVideoPlayer) {
|
||||
players[player]?.remove(view.nitroId)
|
||||
if(moveToLatestView) maybePassPlayerToView(player)
|
||||
|
||||
// If this was the last view using this player, clean up
|
||||
if (players[player]?.isEmpty() == true) {
|
||||
players.remove(player)
|
||||
} else {
|
||||
// If there are other views using this player, move to the latest one
|
||||
maybePassPlayerToView(player)
|
||||
}
|
||||
}
|
||||
|
||||
fun registerPlayer(player: HybridVideoPlayer) {
|
||||
players[player] = players.getOrDefault(player, mutableListOf())
|
||||
if (!players.containsKey(player)) {
|
||||
players[player] = mutableListOf()
|
||||
}
|
||||
}
|
||||
|
||||
fun unregisterPlayer(player: HybridVideoPlayer) {
|
||||
// clear player from all views
|
||||
val views = players[player]?.mapNotNull { getVideoViewWeakReferenceByNitroId(it)?.get() } ?: return
|
||||
|
||||
views.forEach { view ->
|
||||
// We are destroying player, so we don't need to look for a new view
|
||||
removeViewFromPlayer(view, player, moveToLatestView = false)
|
||||
}
|
||||
|
||||
// Clear player from views
|
||||
views.forEach {
|
||||
it.hybridPlayer = null
|
||||
}
|
||||
|
||||
players.remove(player)
|
||||
}
|
||||
|
||||
fun registerFullscreenActivity(activity: FullscreenVideoViewActivity, id: Int) {
|
||||
fullscreenActivities[id] = WeakReference(activity)
|
||||
fun getPlayerByNitroId(nitroId: Int): HybridVideoPlayer? {
|
||||
return players.keys.find { player ->
|
||||
players[player]?.contains(nitroId) == true
|
||||
}
|
||||
}
|
||||
|
||||
fun unregisterFullscreenActivity(id: Int, player: HybridVideoPlayer?, moveToLatestView: Boolean = true) {
|
||||
fullscreenActivities.remove(id)
|
||||
|
||||
if (player != null && moveToLatestView) {
|
||||
maybePassPlayerToView(player)
|
||||
fun updateVideoViewNitroId(oldNitroId: Int, newNitroId: Int, view: VideoView) {
|
||||
// Remove old mapping
|
||||
if (oldNitroId != -1) {
|
||||
views.remove(oldNitroId)
|
||||
|
||||
// Update player mappings
|
||||
players.keys.forEach { player ->
|
||||
players[player]?.let { nitroIds ->
|
||||
if (nitroIds.remove(oldNitroId)) {
|
||||
nitroIds.add(newNitroId)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Add new mapping
|
||||
views[newNitroId] = WeakReference(view)
|
||||
}
|
||||
|
||||
fun getVideoViewWeakReferenceByNitroId(nitroId: Int): WeakReference<VideoView>? {
|
||||
return views[nitroId]
|
||||
}
|
||||
|
||||
fun updateVideoViewNitroId(oldNitroId: Int, newNitroId: Int, view: VideoView) {
|
||||
// Update view in views map
|
||||
views.remove(oldNitroId)
|
||||
views[newNitroId] = WeakReference(view)
|
||||
|
||||
// Update view in players map
|
||||
players.forEach { (_, nitroIds) ->
|
||||
// replace old id with new id (keep order)
|
||||
val index = nitroIds.indexOf(oldNitroId)
|
||||
|
||||
if (index != -1) {
|
||||
nitroIds[index] = newNitroId
|
||||
}
|
||||
}
|
||||
|
||||
// Update view in fullscreen activities map
|
||||
fullscreenActivities.forEach { (_, activity) ->
|
||||
if (activity.get()?.videoViewNitroId == oldNitroId) {
|
||||
activity.get()?.videoViewNitroId = newNitroId
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun getPlayerByNitroId(nitroId: Int): HybridVideoPlayer? {
|
||||
return players.entries.firstOrNull { it.value.contains(nitroId) }?.key
|
||||
}
|
||||
}
|
||||
|
||||
-126
@@ -1,126 +0,0 @@
|
||||
package com.video.core.activities
|
||||
|
||||
import android.annotation.SuppressLint
|
||||
import android.app.Activity
|
||||
import android.app.PictureInPictureParams
|
||||
import android.content.res.Configuration
|
||||
import android.os.Build
|
||||
import android.os.Bundle
|
||||
import android.util.Log
|
||||
import android.util.Rational
|
||||
import android.view.View
|
||||
import android.view.WindowInsets
|
||||
import android.view.WindowInsetsController
|
||||
import android.widget.ImageButton
|
||||
import androidx.annotation.RequiresApi
|
||||
import androidx.media3.common.util.UnstableApi
|
||||
import androidx.media3.ui.PlayerView
|
||||
import com.margelo.nitro.video.HybridVideoPlayer
|
||||
import com.video.R
|
||||
import com.video.core.VideoManager
|
||||
import com.video.core.utils.PictureInPictureUtils.calculateAspectRatio
|
||||
import com.video.core.utils.PictureInPictureUtils.calculateSourceRectHint
|
||||
import com.video.view.VideoView
|
||||
import java.lang.ref.WeakReference
|
||||
|
||||
@UnstableApi
|
||||
class FullscreenVideoViewActivity : Activity() {
|
||||
private lateinit var container: View
|
||||
lateinit var playerView: PlayerView
|
||||
|
||||
var videoViewNitroId: Int = -1
|
||||
private var videoView: WeakReference<VideoView>? = null
|
||||
|
||||
private lateinit var player: HybridVideoPlayer
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
setContentView(R.layout.fullscreen_video_view_activity)
|
||||
container = findViewById(R.id.fullscreen_container)
|
||||
playerView = findViewById(R.id.player_view)
|
||||
|
||||
try {
|
||||
videoViewNitroId = intent.getIntExtra("nitroId", -1)
|
||||
|
||||
if (videoViewNitroId == -1) throw Exception("nitroId not found")
|
||||
|
||||
videoView = VideoManager.getVideoViewWeakReferenceByNitroId(videoViewNitroId)
|
||||
|
||||
player = VideoManager.getPlayerByNitroId(videoViewNitroId)
|
||||
?: throw Exception("Player not found")
|
||||
|
||||
player.moveToFullscreenActivity(this)
|
||||
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
|
||||
val params = PictureInPictureParams.Builder()
|
||||
.setAutoEnterEnabled(videoView?.get()?.autoEnterPictureInPicture == true)
|
||||
.setSourceRectHint(calculateSourceRectHint(playerView))
|
||||
.setAspectRatio(calculateAspectRatio(playerView))
|
||||
.build()
|
||||
|
||||
setPictureInPictureParams(params)
|
||||
}
|
||||
} catch (error: Error) {
|
||||
Log.e("ReactNativeVideo - FullscreenVideoViewActivity", error.message, error)
|
||||
finish()
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
override fun onPictureInPictureModeChanged(isInPictureInPictureMode: Boolean, newConfig: Configuration) {
|
||||
super.onPictureInPictureModeChanged(isInPictureInPictureMode, newConfig)
|
||||
|
||||
if (isInPictureInPictureMode) {
|
||||
playerView.useController = false
|
||||
} else {
|
||||
playerView.useController = videoView?.get()?.useController == true
|
||||
}
|
||||
}
|
||||
|
||||
override fun onPostCreate(savedInstanceState: Bundle?) {
|
||||
super.onPostCreate(savedInstanceState)
|
||||
setupFullScreenButton()
|
||||
playerView.setShowSubtitleButton(true)
|
||||
hideSystemUI()
|
||||
}
|
||||
|
||||
override fun onDestroy() {
|
||||
super.onDestroy()
|
||||
finish()
|
||||
}
|
||||
|
||||
override fun finish() {
|
||||
super.finish()
|
||||
VideoManager.unregisterFullscreenActivity(hashCode(), player)
|
||||
videoView?.get()?.exitFullscreen()
|
||||
}
|
||||
|
||||
@SuppressLint("PrivateResource")
|
||||
private fun setupFullScreenButton() {
|
||||
playerView.setFullscreenButtonClickListener { _ ->
|
||||
finish()
|
||||
}
|
||||
|
||||
// We need to manually change icon, as we are using separate PlayerView in fullscreen activity
|
||||
val button = playerView.findViewById<ImageButton>(androidx.media3.ui.R.id.exo_fullscreen)
|
||||
button.setImageResource(androidx.media3.ui.R.drawable.exo_icon_fullscreen_exit)
|
||||
}
|
||||
|
||||
@Suppress("DEPRECATION")
|
||||
private fun hideSystemUI() {
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
|
||||
container.fitsSystemWindows = false
|
||||
container.windowInsetsController?.let { controller ->
|
||||
controller.hide(WindowInsets.Type.systemBars())
|
||||
controller.systemBarsBehavior = WindowInsetsController.BEHAVIOR_SHOW_TRANSIENT_BARS_BY_SWIPE
|
||||
}
|
||||
} else {
|
||||
window.decorView.systemUiVisibility = (View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY
|
||||
or View.SYSTEM_UI_FLAG_LAYOUT_STABLE
|
||||
or View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION
|
||||
or View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN
|
||||
or View.SYSTEM_UI_FLAG_HIDE_NAVIGATION
|
||||
or View.SYSTEM_UI_FLAG_FULLSCREEN)
|
||||
}
|
||||
}
|
||||
}
|
||||
+231
@@ -0,0 +1,231 @@
|
||||
package com.video.core.fragments
|
||||
|
||||
import android.annotation.SuppressLint
|
||||
import android.content.res.Configuration
|
||||
import android.os.Build
|
||||
import android.os.Bundle
|
||||
import android.view.LayoutInflater
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import android.view.WindowInsets
|
||||
import android.view.WindowInsetsController
|
||||
import android.widget.FrameLayout
|
||||
import android.widget.ImageButton
|
||||
import androidx.activity.OnBackPressedCallback
|
||||
import androidx.annotation.OptIn
|
||||
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.view.VideoView
|
||||
import java.util.UUID
|
||||
|
||||
@OptIn(UnstableApi::class)
|
||||
class FullscreenVideoFragment(private val videoView: VideoView) : Fragment() {
|
||||
val id: String = UUID.randomUUID().toString()
|
||||
|
||||
private var container: FrameLayout? = null
|
||||
private var originalPlayerParent: ViewGroup? = null
|
||||
private var originalPlayerLayoutParams: ViewGroup.LayoutParams? = null
|
||||
private var rootContentViews: List<View> = listOf()
|
||||
|
||||
// Back press callback to handle back navigation
|
||||
private val backPressCallback = object : OnBackPressedCallback(true) {
|
||||
override fun handleOnBackPressed() {
|
||||
videoView.exitFullscreen()
|
||||
}
|
||||
}
|
||||
|
||||
override fun onCreateView(
|
||||
inflater: LayoutInflater,
|
||||
container: ViewGroup?,
|
||||
savedInstanceState: Bundle?
|
||||
): View? {
|
||||
// Create a fullscreen container
|
||||
this.container = FrameLayout(requireContext()).apply {
|
||||
layoutParams = ViewGroup.LayoutParams(
|
||||
ViewGroup.LayoutParams.MATCH_PARENT,
|
||||
ViewGroup.LayoutParams.MATCH_PARENT
|
||||
)
|
||||
setBackgroundColor(android.graphics.Color.BLACK)
|
||||
keepScreenOn = true
|
||||
}
|
||||
return this.container
|
||||
}
|
||||
|
||||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||
super.onViewCreated(view, savedInstanceState)
|
||||
|
||||
// Register back press callback
|
||||
requireActivity().onBackPressedDispatcher.addCallback(viewLifecycleOwner, backPressCallback)
|
||||
|
||||
enterFullscreenMode()
|
||||
setupPlayerView()
|
||||
hideSystemUI()
|
||||
|
||||
// Update PiP params if supported
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
|
||||
try {
|
||||
val params = createPictureInPictureParams(videoView)
|
||||
requireActivity().setPictureInPictureParams(params)
|
||||
} catch (_: Exception) {}
|
||||
}
|
||||
}
|
||||
|
||||
override fun onConfigurationChanged(newConfig: Configuration) {
|
||||
super.onConfigurationChanged(newConfig)
|
||||
|
||||
// Handle PiP mode changes
|
||||
val isInPictureInPictureMode =
|
||||
requireActivity().isInPictureInPictureMode
|
||||
|
||||
if (isInPictureInPictureMode) {
|
||||
videoView.playerView.useController = false
|
||||
} else {
|
||||
videoView.playerView.useController = videoView.useController
|
||||
}
|
||||
}
|
||||
|
||||
private fun enterFullscreenMode() {
|
||||
// Store original parent and layout params
|
||||
originalPlayerParent = videoView.playerView.parent as? ViewGroup
|
||||
originalPlayerLayoutParams = videoView.playerView.layoutParams
|
||||
|
||||
// Remove player from original parent
|
||||
originalPlayerParent?.removeView(videoView.playerView)
|
||||
|
||||
// Hide all root content views
|
||||
val currentActivity = requireActivity()
|
||||
val rootContent = currentActivity.window.decorView.findViewById<ViewGroup>(android.R.id.content)
|
||||
rootContentViews = (0 until rootContent.childCount)
|
||||
.map { rootContent.getChildAt(it) }
|
||||
.filter { it.isVisible }
|
||||
|
||||
rootContentViews.forEach { view ->
|
||||
view.visibility = View.GONE
|
||||
}
|
||||
|
||||
// Add our fullscreen container to root
|
||||
rootContent.addView(container,
|
||||
ViewGroup.LayoutParams(
|
||||
ViewGroup.LayoutParams.MATCH_PARENT,
|
||||
ViewGroup.LayoutParams.MATCH_PARENT
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
private fun setupPlayerView() {
|
||||
// Add PlayerView to our container
|
||||
container?.addView(videoView.playerView,
|
||||
FrameLayout.LayoutParams(
|
||||
FrameLayout.LayoutParams.MATCH_PARENT,
|
||||
FrameLayout.LayoutParams.MATCH_PARENT
|
||||
)
|
||||
)
|
||||
|
||||
videoView.playerView.setBackgroundColor(android.graphics.Color.BLACK)
|
||||
videoView.playerView.setShutterBackgroundColor(android.graphics.Color.BLACK)
|
||||
|
||||
// We need show controls in fullscreen
|
||||
videoView.playerView.useController = true
|
||||
|
||||
setupFullscreenButton()
|
||||
videoView.playerView.setShowSubtitleButton(true)
|
||||
}
|
||||
|
||||
@SuppressLint("PrivateResource")
|
||||
private fun setupFullscreenButton() {
|
||||
videoView.playerView.setFullscreenButtonClickListener { _ ->
|
||||
videoView.exitFullscreen()
|
||||
}
|
||||
|
||||
// Change icon to exit fullscreen
|
||||
val button = videoView.playerView.findViewById<ImageButton>(androidx.media3.ui.R.id.exo_fullscreen)
|
||||
button?.setImageResource(androidx.media3.ui.R.drawable.exo_icon_fullscreen_exit)
|
||||
}
|
||||
|
||||
@Suppress("DEPRECATION")
|
||||
private fun hideSystemUI() {
|
||||
val currentActivity = requireActivity()
|
||||
container?.let { container ->
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
|
||||
container.fitsSystemWindows = false
|
||||
container.windowInsetsController?.let { controller ->
|
||||
controller.hide(WindowInsets.Type.systemBars())
|
||||
controller.systemBarsBehavior = WindowInsetsController.BEHAVIOR_SHOW_TRANSIENT_BARS_BY_SWIPE
|
||||
}
|
||||
} else {
|
||||
currentActivity.window.decorView.systemUiVisibility = (
|
||||
View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY
|
||||
or View.SYSTEM_UI_FLAG_LAYOUT_STABLE
|
||||
or View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION
|
||||
or View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN
|
||||
or View.SYSTEM_UI_FLAG_HIDE_NAVIGATION
|
||||
or View.SYSTEM_UI_FLAG_FULLSCREEN
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Suppress("DEPRECATION")
|
||||
private fun restoreSystemUI() {
|
||||
val currentActivity = requireActivity()
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
|
||||
container?.windowInsetsController?.show(WindowInsets.Type.systemBars())
|
||||
} else {
|
||||
currentActivity.window.decorView.systemUiVisibility = View.SYSTEM_UI_FLAG_VISIBLE
|
||||
}
|
||||
}
|
||||
|
||||
fun exitFullscreen() {
|
||||
// Remove back press callback since we're exiting
|
||||
backPressCallback.remove()
|
||||
|
||||
restoreSystemUI()
|
||||
|
||||
if (videoView.useController == false) {
|
||||
videoView.playerView.useController = false
|
||||
}
|
||||
|
||||
// Ensure PlayerView keeps black background when returning to normal mode
|
||||
videoView.playerView.setBackgroundColor(android.graphics.Color.BLACK)
|
||||
videoView.playerView.setShutterBackgroundColor(android.graphics.Color.BLACK)
|
||||
|
||||
// Remove PlayerView from our container
|
||||
container?.removeView(videoView.playerView)
|
||||
|
||||
// Remove our container from root
|
||||
val currentActivity = requireActivity()
|
||||
val rootContent = currentActivity.window.decorView.findViewById<ViewGroup>(android.R.id.content)
|
||||
rootContent.removeView(container)
|
||||
|
||||
// Restore root content views
|
||||
rootContentViews.forEach { it.visibility = View.VISIBLE }
|
||||
rootContentViews = listOf()
|
||||
|
||||
// Safely restore PlayerView to original parent
|
||||
// First, ensure PlayerView is removed from any current parent
|
||||
val currentParent = videoView.playerView.parent as? ViewGroup
|
||||
currentParent?.removeView(videoView.playerView)
|
||||
|
||||
// Now add it back to the original parent
|
||||
originalPlayerParent?.addView(videoView.playerView, originalPlayerLayoutParams)
|
||||
|
||||
// Remove this fragment
|
||||
parentFragmentManager.beginTransaction()
|
||||
.remove(this)
|
||||
.commitAllowingStateLoss()
|
||||
|
||||
// Notify VideoView that we've exited fullscreen
|
||||
videoView.isInFullscreen = false
|
||||
}
|
||||
|
||||
override fun onDestroy() {
|
||||
super.onDestroy()
|
||||
|
||||
// Ensure we clean up properly if fragment is destroyed
|
||||
if (videoView.isInFullscreen) {
|
||||
exitFullscreen()
|
||||
}
|
||||
}
|
||||
}
|
||||
-10
@@ -28,7 +28,6 @@ import com.margelo.nitro.core.Promise
|
||||
import com.video.core.LibraryError
|
||||
import com.video.core.PlayerError
|
||||
import com.video.core.VideoManager
|
||||
import com.video.core.activities.FullscreenVideoViewActivity
|
||||
import com.video.core.player.OnAudioFocusChangedListener
|
||||
import com.video.core.recivers.AudioBecomingNoisyReceiver
|
||||
import com.video.core.utils.Threading.runOnMainThread
|
||||
@@ -295,15 +294,6 @@ class HybridVideoPlayer() : HybridVideoPlayerSpec() {
|
||||
}
|
||||
}
|
||||
|
||||
fun moveToFullscreenActivity(activity: FullscreenVideoViewActivity) {
|
||||
VideoManager.registerFullscreenActivity(activity, activity.hashCode())
|
||||
|
||||
runOnMainThreadSync {
|
||||
PlayerView.switchTargetView(playerPointer, currentPlayerView?.get(), activity.playerView)
|
||||
currentPlayerView = WeakReference(activity.playerView)
|
||||
}
|
||||
}
|
||||
|
||||
override val memorySize: Long
|
||||
get() = allocator?.totalBytesAllocated?.toLong() ?: 0L
|
||||
|
||||
|
||||
+1
-1
@@ -35,7 +35,7 @@ class HybridVideoViewViewManager(nitroId: Int): HybridVideoViewViewManagerSpec()
|
||||
}
|
||||
|
||||
override fun exitFullscreen() {
|
||||
throw LibraryError.MethodNotSupported("exitFullscreen")
|
||||
videoView.get()?.exitFullscreen()
|
||||
}
|
||||
|
||||
override fun enterPictureInPicture() {
|
||||
|
||||
@@ -2,7 +2,6 @@ package com.video.view
|
||||
|
||||
import android.annotation.SuppressLint
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import android.graphics.Color
|
||||
import android.os.Build
|
||||
import android.util.AttributeSet
|
||||
@@ -22,7 +21,7 @@ import com.margelo.nitro.video.VideoViewEvents
|
||||
import com.video.core.LibraryError
|
||||
import com.video.core.VideoManager
|
||||
import com.video.core.VideoViewError
|
||||
import com.video.core.activities.FullscreenVideoViewActivity
|
||||
import com.video.core.fragments.FullscreenVideoFragment
|
||||
import com.video.core.fragments.PictureInPictureHelperFragment
|
||||
import com.video.core.utils.PictureInPictureUtils.canEnterPictureInPicture
|
||||
import com.video.core.utils.PictureInPictureUtils.createPictureInPictureParams
|
||||
@@ -108,6 +107,7 @@ class VideoView @JvmOverloads constructor(
|
||||
}
|
||||
private var rootContentViews: List<View> = listOf()
|
||||
private var pictureInPictureHelperTag: String? = null
|
||||
private var fullscreenFragmentTag: String? = null
|
||||
|
||||
val applicationContent: ReactApplicationContext
|
||||
get() {
|
||||
@@ -136,10 +136,14 @@ class VideoView @JvmOverloads constructor(
|
||||
post(layoutRunnable)
|
||||
}
|
||||
|
||||
@SuppressLint("PrivateResource")
|
||||
private fun setupFullscreenButton() {
|
||||
playerView.setFullscreenButtonClickListener { _ ->
|
||||
enterFullscreen()
|
||||
}
|
||||
|
||||
playerView.findViewById<ImageButton>(androidx.media3.ui.R.id.exo_fullscreen)
|
||||
?.setImageResource(androidx.media3.ui.R.drawable.exo_ic_fullscreen_enter)
|
||||
}
|
||||
|
||||
fun enterFullscreen() {
|
||||
@@ -147,25 +151,54 @@ class VideoView @JvmOverloads constructor(
|
||||
return
|
||||
}
|
||||
|
||||
isInFullscreen = true
|
||||
|
||||
val intent = Intent(context, FullscreenVideoViewActivity::class.java)
|
||||
intent.putExtra("nitroId", nitroId)
|
||||
val currentActivity = applicationContent.currentActivity
|
||||
if (currentActivity !is FragmentActivity) {
|
||||
Log.e("ReactNativeVideo", "Current activity is not a FragmentActivity, cannot enter fullscreen")
|
||||
return
|
||||
}
|
||||
|
||||
try {
|
||||
val currentActivity = applicationContent.currentActivity
|
||||
currentActivity?.startActivity(intent)
|
||||
events.willEnterFullscreen?.let { it() }
|
||||
|
||||
val fragment = FullscreenVideoFragment(this)
|
||||
fullscreenFragmentTag = fragment.id
|
||||
|
||||
currentActivity.supportFragmentManager.beginTransaction()
|
||||
.add(fragment, fragment.id)
|
||||
.commitAllowingStateLoss()
|
||||
|
||||
isInFullscreen = true
|
||||
} catch (err: Exception) {
|
||||
val debugMessage = "Failed to start fullscreen activity for nitroId: $nitroId"
|
||||
val debugMessage = "Failed to start fullscreen fragment for nitroId: $nitroId"
|
||||
Log.e("ReactNativeVideo", debugMessage, err)
|
||||
}
|
||||
}
|
||||
|
||||
@SuppressLint("PrivateResource")
|
||||
fun exitFullscreen() {
|
||||
// Change fullscreen button icon back to enter fullscreen
|
||||
playerView.findViewById<ImageButton>(androidx.media3.ui.R.id.exo_fullscreen)
|
||||
?.setImageResource(androidx.media3.ui.R.drawable.exo_ic_fullscreen_enter)
|
||||
if (!isInFullscreen) {
|
||||
return
|
||||
}
|
||||
|
||||
events.willExitFullscreen?.let { it() }
|
||||
|
||||
val currentActivity = applicationContent.currentActivity
|
||||
fullscreenFragmentTag?.let { tag ->
|
||||
(currentActivity as? FragmentActivity)?.let { activity ->
|
||||
activity.supportFragmentManager.findFragmentByTag(tag)?.let { fragment ->
|
||||
// The fragment will handle its own removal in exitFullscreen()
|
||||
if (fragment is FullscreenVideoFragment) {
|
||||
runOnMainThread {
|
||||
fragment.exitFullscreen()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
fullscreenFragmentTag = null
|
||||
}
|
||||
|
||||
// Change fullscreen button icon back to enter fullscreen and update callback
|
||||
setupFullscreenButton()
|
||||
|
||||
isInFullscreen = false
|
||||
}
|
||||
@@ -199,11 +232,28 @@ class VideoView @JvmOverloads constructor(
|
||||
}
|
||||
}
|
||||
|
||||
private fun removeFullscreenFragment() {
|
||||
val currentActivity = applicationContent.currentActivity
|
||||
fullscreenFragmentTag?.let { tag ->
|
||||
(currentActivity as? FragmentActivity)?.let { activity ->
|
||||
activity.supportFragmentManager.findFragmentByTag(tag)?.let { fragment ->
|
||||
activity.supportFragmentManager.beginTransaction()
|
||||
.remove(fragment)
|
||||
.commitAllowingStateLoss()
|
||||
}
|
||||
}
|
||||
fullscreenFragmentTag = null
|
||||
}
|
||||
}
|
||||
|
||||
fun hideRootContentViews() {
|
||||
// Remove playerView from parent
|
||||
// In PiP mode, we don't want to show the controller
|
||||
// Controls are handled by System if we have MediaSession
|
||||
playerView.useController = false
|
||||
playerView.setBackgroundColor(Color.BLACK)
|
||||
playerView.setShutterBackgroundColor(Color.BLACK)
|
||||
|
||||
(playerView.parent as? ViewGroup)?.removeView(playerView)
|
||||
|
||||
val currentActivity = applicationContent.currentActivity ?: return
|
||||
@@ -225,6 +275,9 @@ class VideoView @JvmOverloads constructor(
|
||||
// Reset PlayerView settings
|
||||
playerView.useController = useController
|
||||
|
||||
playerView.setBackgroundColor(Color.BLACK)
|
||||
playerView.setShutterBackgroundColor(Color.BLACK)
|
||||
|
||||
val currentActivity = applicationContent.currentActivity ?: return
|
||||
val rootContent = currentActivity.window.decorView.findViewById<ViewGroup>(android.R.id.content)
|
||||
rootContent.removeView(playerView)
|
||||
@@ -273,6 +326,7 @@ class VideoView @JvmOverloads constructor(
|
||||
// -------- View Lifecycle Methods --------
|
||||
override fun onDetachedFromWindow() {
|
||||
removePipHelper()
|
||||
removeFullscreenFragment()
|
||||
VideoManager.unregisterView(this)
|
||||
super.onDetachedFromWindow()
|
||||
}
|
||||
|
||||
-14
@@ -1,14 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:id="@+id/fullscreen_container"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:background="@android:color/black"
|
||||
android:keepScreenOn="true"
|
||||
tools:context=".core.activities.FullscreenVideoViewActivity">
|
||||
<androidx.media3.ui.PlayerView
|
||||
android:id="@+id/player_view"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent" />
|
||||
</FrameLayout>
|
||||
Reference in New Issue
Block a user