From 88c20d1c065b2778a66ddde4a3b92bd58bec043b Mon Sep 17 00:00:00 2001 From: Wojciech Ogrodowczyk Date: Wed, 11 Jun 2025 09:33:38 +0200 Subject: [PATCH] feat(ios): forward real fullscreen events from AVPlayer instead of guessing (#4509) --- ios/Video/Features/RCTPlayerObserver.swift | 23 ++++++++++++-- ios/Video/RCTVideo.swift | 36 ++++++++++------------ 2 files changed, 37 insertions(+), 22 deletions(-) diff --git a/ios/Video/Features/RCTPlayerObserver.swift b/ios/Video/Features/RCTPlayerObserver.swift index 230ff768..6462c512 100644 --- a/ios/Video/Features/RCTPlayerObserver.swift +++ b/ios/Video/Features/RCTPlayerObserver.swift @@ -31,6 +31,10 @@ protocol RCTPlayerObserverHandler: RCTPlayerObserverHandlerObjc { func handlePictureInPictureEnter() func handlePictureInPictureExit() func handleRestoreUserInterfaceForPictureInPictureStop() + func handleWillEnterFullScreen() + func handleDidEnterFullScreen() + func handleWillExitFullScreen() + func handleDidExitFullScreen() } // MARK: - RCTPlayerObserver @@ -323,17 +327,32 @@ class RCTPlayerObserver: NSObject, AVPlayerItemMetadataOutputPushDelegate, AVPla } #if !os(tvOS) + func playerViewController( + _: AVPlayerViewController, + willBeginFullScreenPresentationWithAnimationCoordinator coordinator: UIViewControllerTransitionCoordinator + ) { + self._handlers?.handleWillEnterFullScreen() + coordinator.animate(alongsideTransition: nil) { [weak self] context in + guard let self, !context.isCancelled else { return } + self._handlers?.handleDidEnterFullScreen() + } + } + func playerViewController( _: AVPlayerViewController, willEndFullScreenPresentationWithAnimationCoordinator coordinator: UIViewControllerTransitionCoordinator ) { + self._handlers?.handleWillExitFullScreen() // iOS automatically pauses videos after exiting fullscreen, // but it's better if we resume playback let wasPlaying = player?.timeControlStatus == .playing coordinator.animate(alongsideTransition: nil) { [weak self] context in - guard let self, !context.isCancelled, wasPlaying else { return } - self.player?.play() + guard let self, !context.isCancelled else { return } + self._handlers?.handleDidExitFullScreen() + if wasPlaying == true { + self.player?.play() + } } } #endif diff --git a/ios/Video/RCTVideo.swift b/ios/Video/RCTVideo.swift index 0500cbae..975353c2 100644 --- a/ios/Video/RCTVideo.swift +++ b/ios/Video/RCTVideo.swift @@ -55,7 +55,6 @@ class RCTVideo: UIView, RCTVideoPlayerViewControllerDelegate, RCTPlayerObserverH private var _fullscreenAutorotate = true private var _fullscreenOrientation: String = "all" private var _fullscreenPlayerPresented = false - private var _fullscreenUncontrolPlayerPresented = false // to call events switching full screen mode from player controls private var _filterName: String! private var _filterEnabled = false private var _presentingViewController: UIViewController? @@ -1682,25 +1681,6 @@ class RCTVideo: UIView, RCTVideoPlayerViewControllerDelegate, RCTPlayerObserverH guard let bounds = RCTVideoUtils.getCurrentWindow()?.bounds else { return } if !oldRect!.equalTo(newRect!) { - // https://github.com/TheWidlarzGroup/react-native-video/issues/3085#issuecomment-1557293391 - if newRect!.equalTo(bounds) { - RCTLog("in fullscreen") - if !_fullscreenUncontrolPlayerPresented { - _fullscreenUncontrolPlayerPresented = true - - self.onVideoFullscreenPlayerWillPresent?(["target": self.reactTag as Any]) - self.onVideoFullscreenPlayerDidPresent?(["target": self.reactTag as Any]) - } - } else { - NSLog("not fullscreen") - if _fullscreenUncontrolPlayerPresented { - _fullscreenUncontrolPlayerPresented = false - - self.onVideoFullscreenPlayerWillDismiss?(["target": self.reactTag as Any]) - self.onVideoFullscreenPlayerDidDismiss?(["target": self.reactTag as Any]) - } - } - if let reactVC = self.reactViewController() { reactVC.view.frame = bounds reactVC.view.setNeedsLayout() @@ -1708,6 +1688,22 @@ class RCTVideo: UIView, RCTVideoPlayerViewControllerDelegate, RCTPlayerObserverH } } + func handleWillEnterFullScreen() { + self.onVideoFullscreenPlayerWillPresent?(["target": self.reactTag as Any]) + } + + func handleDidEnterFullScreen() { + self.onVideoFullscreenPlayerDidPresent?(["target": self.reactTag as Any]) + } + + func handleWillExitFullScreen() { + self.onVideoFullscreenPlayerWillDismiss?(["target": self.reactTag as Any]) + } + + func handleDidExitFullScreen() { + self.onVideoFullscreenPlayerDidDismiss?(["target": self.reactTag as Any]) + } + @objc func handleDidFailToFinishPlaying(notification: NSNotification!) { guard onVideoError != nil else { return }