diff --git a/packages/react-native-video/android/src/main/java/com/twg/video/core/fragments/FullscreenVideoFragment.kt b/packages/react-native-video/android/src/main/java/com/twg/video/core/fragments/FullscreenVideoFragment.kt index 199b680a..868e2540 100644 --- a/packages/react-native-video/android/src/main/java/com/twg/video/core/fragments/FullscreenVideoFragment.kt +++ b/packages/react-native-video/android/src/main/java/com/twg/video/core/fragments/FullscreenVideoFragment.kt @@ -205,8 +205,8 @@ class FullscreenVideoFragment(private val videoView: VideoView) : Fragment() { videoView.playerView.setBackgroundColor(android.graphics.Color.BLACK) videoView.playerView.setShutterBackgroundColor(android.graphics.Color.BLACK) - // Remove PlayerView from our container - container?.removeView(videoView.playerView) + // Remove PlayerView from our container + container?.removeView(videoView.playerView) // Remove our container from root val currentActivity = requireActivity() @@ -222,8 +222,10 @@ class FullscreenVideoFragment(private val videoView: VideoView) : Fragment() { val currentParent = videoView.playerView.parent as? ViewGroup currentParent?.removeView(videoView.playerView) - // Now add it back to the original parent - originalPlayerParent?.addView(videoView.playerView, originalPlayerLayoutParams) + // Now add it back to the original parent if it's not already the parent + if (videoView.playerView.parent != originalPlayerParent) { + originalPlayerParent?.addView(videoView.playerView, originalPlayerLayoutParams) + } // Remove this fragment parentFragmentManager.beginTransaction() diff --git a/packages/react-native-video/android/src/main/java/com/twg/video/core/fragments/PictureInPictureHelperFragment.kt b/packages/react-native-video/android/src/main/java/com/twg/video/core/fragments/PictureInPictureHelperFragment.kt index d54074c7..b3a21c23 100644 --- a/packages/react-native-video/android/src/main/java/com/twg/video/core/fragments/PictureInPictureHelperFragment.kt +++ b/packages/react-native-video/android/src/main/java/com/twg/video/core/fragments/PictureInPictureHelperFragment.kt @@ -50,6 +50,16 @@ class PictureInPictureHelperFragment(private val videoView: VideoView) : Fragmen } if (currentPipVideo == videoView) { + // If we're currently in fullscreen, exit it first to prevent parent conflicts + if (videoView.isInFullscreen) { + try { + videoView.exitFullscreen() + } catch (e: Exception) { + Log.w("ReactNativeVideo", "Failed to exit fullscreen before entering PiP for nitroId: ${videoView.nitroId}", e) + } + } + + // Now move the PlayerView to the root for PiP and hide content videoView.hideRootContentViews() videoView.isInPictureInPicture = true } diff --git a/packages/react-native-video/android/src/main/java/com/twg/video/view/VideoView.kt b/packages/react-native-video/android/src/main/java/com/twg/video/view/VideoView.kt index b56cd63a..26c6880d 100644 --- a/packages/react-native-video/android/src/main/java/com/twg/video/view/VideoView.kt +++ b/packages/react-native-video/android/src/main/java/com/twg/video/view/VideoView.kt @@ -136,6 +136,7 @@ class VideoView @JvmOverloads constructor( private var rootContentViews: List = listOf() private var pictureInPictureHelperTag: String? = null private var fullscreenFragmentTag: String? = null + private var movedToRootForPiP: Boolean = false val applicationContent: ReactApplicationContext get() { @@ -298,6 +299,14 @@ class VideoView @JvmOverloads constructor( playerView.setBackgroundColor(Color.BLACK) playerView.setShutterBackgroundColor(Color.BLACK) + // If we're already in fullscreen, the PlayerView is inside the fullscreen container + // and root content is already hidden. Avoid moving the PlayerView again. + if (isInFullscreen) { + Log.d("ReactNativeVideo", "PiP entered while in fullscreen - skipping reparent to root for nitroId: $nitroId") + movedToRootForPiP = false + return + } + (playerView.parent as? ViewGroup)?.removeView(playerView) val currentActivity = applicationContent.currentActivity ?: return @@ -306,13 +315,13 @@ class VideoView @JvmOverloads constructor( .map { rootContent.getChildAt(it) } .filter { it.isVisible } - rootContentViews.forEach { view -> - view.visibility = GONE - } + rootContentViews.forEach { view -> view.visibility = GONE } - rootContent.addView(playerView, + rootContent.addView( + playerView, LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT) ) + movedToRootForPiP = true Log.d("ReactNativeVideo", "Successfully moved player view to root content for PiP nitroId: $nitroId") } @@ -326,16 +335,24 @@ class VideoView @JvmOverloads constructor( playerView.setBackgroundColor(Color.BLACK) playerView.setShutterBackgroundColor(Color.BLACK) - val currentActivity = applicationContent.currentActivity ?: return - val rootContent = currentActivity.window.decorView.findViewById(android.R.id.content) - rootContent.removeView(playerView) + if (movedToRootForPiP) { + (playerView.parent as? ViewGroup)?.removeView(playerView) - // Restore root content views - rootContentViews.forEach { it.visibility = View.VISIBLE } - rootContentViews = listOf() + val currentActivity = applicationContent.currentActivity ?: return + val rootContent = currentActivity.window.decorView.findViewById(android.R.id.content) - // Add PlayerView back to VideoView - addView(playerView) + rootContent.removeView(playerView) + + // Restore root content views + rootContentViews.forEach { it.visibility = View.VISIBLE } + rootContentViews = listOf() + movedToRootForPiP = false + } + + // Add PlayerView back to this VideoView only if not already attached + if (playerView.parent != this) { + addView(playerView) + } Log.d("ReactNativeVideo", "Successfully restored root content views for video nitroId: $nitroId") } @@ -396,20 +413,24 @@ class VideoView @JvmOverloads constructor( } fun exitPictureInPicture() { - if (!isInPictureInPicture || isInFullscreen) { - return - } - + if (!isInPictureInPicture) return + VideoManager.notifyPictureInPictureExited(this) - + events.willExitPictureInPicture?.let { it() } - restoreRootContentViews() + + if (movedToRootForPiP) { + restoreRootContentViews() + } else { + Log.d("ReactNativeVideo", "Exiting PiP while in fullscreen - no reparent needed for nitroId: $nitroId") + } + isInPictureInPicture = false } internal fun forceExitPictureInPicture() { - if (!isInPictureInPicture || isInFullscreen) { - Log.d("ReactNativeVideo", "Force exit PiP skipped for nitroId: $nitroId (not in PiP or in fullscreen)") + if (!isInPictureInPicture) { + Log.d("ReactNativeVideo", "Force exit PiP skipped for nitroId: $nitroId (not in PiP)") return } @@ -422,7 +443,11 @@ class VideoView @JvmOverloads constructor( } events.willExitPictureInPicture?.let { it() } - restoreRootContentViews() + if (movedToRootForPiP) { + restoreRootContentViews() + } else { + Log.d("ReactNativeVideo", "Force exit PiP while in fullscreen - no reparent needed for nitroId: $nitroId") + } isInPictureInPicture = false VideoManager.notifyPictureInPictureExited(this)