What happened?
When a video is started in fullscreen mode (via enterFullscreen()) and the user enters
Picture-in-Picture, then taps the restore button on the PiP window to return to the app, the
player briefly flashes back to fullscreen and then immediately dismisses with a closing animation.
The fullscreen presentation is gone, audio may continue playing, and the player is left in a broken
state where subsequent playback may not work. PiP started from inline video (not fullscreen) is
unaffected.
Root cause
VideoComponentView presents the AVPlayerViewController directly via the underlying
enterFullscreen(animated:) private API on a child-VC-attached controller. AVKit treats the
presentation as its own private fullscreen and, at PiP-start, dismisses it as part of moving the
player UI into the PiP window. By the time PiP starts the controller has
presentingViewController = nil, view.superview = nil, view.window = nil - fully orphaned.
PiP-stop then has nowhere to restore the player UI, so AVKit silently fails the restoration or
briefly attaches and tears down. Confirmed via hierarchy-state tracing:
[PiPDebug] willStart PiP - presenting=Optional(<RCTFabricModalHostViewController>)
[PiPDebug] willEndFullScreen COMPLETION - presenting=nil view.superview=nil view.window=nil
playerViewControllerRestoreUserInterfaceForPictureInPictureStop is also never called in this
configuration, because AVKit considers the dismissal complete rather than partial.
Proposed fix
Introduce a thin FullscreenHostViewController (plain UIViewController) that hosts the
AVPlayerViewController as a child and is presented modally for fullscreen; AVKit's own fullscreen
is layered on top via enterFullscreen(animated:). The host stays presented as a stable parent VC
across all PiP transitions, giving AVKit a place to put the player UI back. Supporting changes:
re-engage AVKit fullscreen in playerViewControllerDidStopPictureInPicture; route JS-driven
exitFullscreen() through AVKit's exitFullscreen(animated:); poll for AVKit's private fullscreen
teardown before dismissing the host modal; strong-capture the host across the dismiss delay (RN may
deallocate VideoComponentView during AVKit's collapse animation).
Scope: tested extensively on iPad (multiple PiP cycles, all dismissal paths). Not tested on
iPhone - the diagnosis is structural, so the fix is expected to apply. PR coming with the fix.
Steps to reproduce
- Mount a
VideoView with allowsPictureInPicturePlayback={true}.
- Call
videoRef.current?.enterFullscreen() to enter fullscreen.
- Start playback.
- Either tap the system PiP button, or background the app (with
autoEnterPictureInPicture={true}).
- Tap the restore button on the PiP window to return to the app.
Expected: player returns to fullscreen and continues playing. Actual: player briefly appears in fullscreen, then immediately animates closed; audio may continue; subsequent playback may fail.
Reproduction repository
No response
react-native-video version
7.0.0-beta.9
react-native version
No response
react-native-nitro-modules version
No response
Platforms
iOS
OS version
iPadOS 17+
Device
Real device
Architecture
New Architecture
Expo
No response
Last working version
No response
Media / source type
No response
What happened?
When a video is started in fullscreen mode (via
enterFullscreen()) and the user entersPicture-in-Picture, then taps the restore button on the PiP window to return to the app, the
player briefly flashes back to fullscreen and then immediately dismisses with a closing animation.
The fullscreen presentation is gone, audio may continue playing, and the player is left in a broken
state where subsequent playback may not work. PiP started from inline video (not fullscreen) is
unaffected.
Root cause
VideoComponentViewpresents theAVPlayerViewControllerdirectly via the underlyingenterFullscreen(animated:)private API on a child-VC-attached controller. AVKit treats thepresentation as its own private fullscreen and, at PiP-start, dismisses it as part of moving the
player UI into the PiP window. By the time PiP starts the controller has
presentingViewController = nil,view.superview = nil,view.window = nil- fully orphaned.PiP-stop then has nowhere to restore the player UI, so AVKit silently fails the restoration or
briefly attaches and tears down. Confirmed via hierarchy-state tracing:
playerViewControllerRestoreUserInterfaceForPictureInPictureStopis also never called in thisconfiguration, because AVKit considers the dismissal complete rather than partial.
Proposed fix
Introduce a thin
FullscreenHostViewController(plainUIViewController) that hosts theAVPlayerViewControlleras a child and is presented modally for fullscreen; AVKit's own fullscreenis layered on top via
enterFullscreen(animated:). The host stays presented as a stable parent VCacross all PiP transitions, giving AVKit a place to put the player UI back. Supporting changes:
re-engage AVKit fullscreen in
playerViewControllerDidStopPictureInPicture; route JS-drivenexitFullscreen()through AVKit'sexitFullscreen(animated:); poll for AVKit's private fullscreenteardown before dismissing the host modal; strong-capture the host across the dismiss delay (RN may
deallocate
VideoComponentViewduring AVKit's collapse animation).Scope: tested extensively on iPad (multiple PiP cycles, all dismissal paths). Not tested on
iPhone - the diagnosis is structural, so the fix is expected to apply. PR coming with the fix.
Steps to reproduce
VideoViewwithallowsPictureInPicturePlayback={true}.videoRef.current?.enterFullscreen()to enter fullscreen.autoEnterPictureInPicture={true}).Expected: player returns to fullscreen and continues playing. Actual: player briefly appears in fullscreen, then immediately animates closed; audio may continue; subsequent playback may fail.
Reproduction repository
No response
react-native-video version
7.0.0-beta.9
react-native version
No response
react-native-nitro-modules version
No response
Platforms
iOS
OS version
iPadOS 17+
Device
Real device
Architecture
New Architecture
Expo
No response
Last working version
No response
Media / source type
No response