Skip to content

[BUG] Player dismisses on PiP restore when started in fullscreen mode (iPad) #4897

Description

@TimUnderhay

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

  1. Mount a VideoView with allowsPictureInPicturePlayback={true}.
  2. Call videoRef.current?.enterFullscreen() to enter fullscreen.
  3. Start playback.
  4. Either tap the system PiP button, or background the app (with autoEnterPictureInPicture={true}).
  5. 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

Metadata

Metadata

Assignees

No one assigned

    Type

    No type
    No fields configured for issues without a type.

    Projects

    Status
    To Triage

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions