diff --git a/android/src/main/java/com/brentvatne/exoplayer/ReactExoplayerView.java b/android/src/main/java/com/brentvatne/exoplayer/ReactExoplayerView.java index ebecbdde0d..dd4f0131b6 100644 --- a/android/src/main/java/com/brentvatne/exoplayer/ReactExoplayerView.java +++ b/android/src/main/java/com/brentvatne/exoplayer/ReactExoplayerView.java @@ -305,7 +305,9 @@ private void createViews() { @Override protected void onAttachedToWindow() { super.onAttachedToWindow(); - initializePlayer(); + // uncommented `initializePlayer()`, because it caused video to restart when it is running + // behind another screen and the user navigated back to the screen where the video runs + // initializePlayer(); } @Override @@ -1497,7 +1499,8 @@ public void setSrc(final Uri uri, final long startTimeMs, final long endTimeMs, DataSourceUtil.getDefaultDataSourceFactory(this.themedReactContext, bandwidthMeter, this.requestHeaders); - if (!isSourceEqual) { + if (!isSourceEqual) { + Log.d("PLAYER", "reload1"); reloadSource(); } } @@ -1537,6 +1540,7 @@ public void setRawSrc(final Uri uri, final String extension) { this.mediaDataSourceFactory = buildDataSourceFactory(true); if (!isSourceEqual) { + Log.d("PLAYER", "reload2"); reloadSource(); } } @@ -1544,6 +1548,7 @@ public void setRawSrc(final Uri uri, final String extension) { public void setTextTracks(ReadableArray textTracks) { this.textTracks = textTracks; + Log.d("PLAYER", "reload3"); reloadSource(); } diff --git a/ios/RCTVideo.xcodeproj/project.pbxproj b/ios/RCTVideo.xcodeproj/project.pbxproj index 1665197d03..ae0c5309b3 100644 --- a/ios/RCTVideo.xcodeproj/project.pbxproj +++ b/ios/RCTVideo.xcodeproj/project.pbxproj @@ -13,6 +13,7 @@ 0177D39D27170A7A00F5BE18 /* RCTVideoPlayerViewControllerDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0177D39627170A7A00F5BE18 /* RCTVideoPlayerViewControllerDelegate.swift */; }; 0177D39E27170A7A00F5BE18 /* RCTVideoManager.m in Sources */ = {isa = PBXBuildFile; fileRef = 0177D39727170A7A00F5BE18 /* RCTVideoManager.m */; }; 0177D39F27170A7A00F5BE18 /* RCTVideo.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0177D39927170A7A00F5BE18 /* RCTVideo.swift */; }; + 832E109729C34D2100DD1D3A /* RCTPlayerDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 832E109629C34D2100DD1D3A /* RCTPlayerDelegate.swift */; }; /* End PBXBuildFile section */ /* Begin PBXCopyFilesBuildPhase section */ @@ -49,6 +50,7 @@ 0177D39827170A7A00F5BE18 /* RCTVideo-Bridging-Header.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = "RCTVideo-Bridging-Header.h"; path = "Video/RCTVideo-Bridging-Header.h"; sourceTree = ""; }; 0177D39927170A7A00F5BE18 /* RCTVideo.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = RCTVideo.swift; path = Video/RCTVideo.swift; sourceTree = ""; }; 134814201AA4EA6300B7C361 /* libRCTVideo.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = libRCTVideo.a; sourceTree = BUILT_PRODUCTS_DIR; }; + 83064F5229C350FE0060F947 /* RCTPlayerDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = RCTPlayerDelegate.swift; path = Video/RCTPlayerDelegate.swift; sourceTree = ""; }; /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ @@ -87,6 +89,7 @@ 58B511D21A9E6C8500147676 = { isa = PBXGroup; children = ( + 83064F5229C350FE0060F947 /* RCTPlayerDelegate.swift */, 01489050272001A100E69940 /* DataStructures */, 01489051272001A100E69940 /* Features */, 0177D39527170A7A00F5BE18 /* RCTSwiftLog */, @@ -189,6 +192,7 @@ 0177D39B27170A7A00F5BE18 /* UIView+FindUIViewController.swift in Sources */, 0177D39F27170A7A00F5BE18 /* RCTVideo.swift in Sources */, 0177D39E27170A7A00F5BE18 /* RCTVideoManager.m in Sources */, + 832E109729C34D2100DD1D3A /* RCTPlayerDelegate.swift in Sources */, 0177D39A27170A7A00F5BE18 /* RCTVideoManager.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; diff --git a/ios/Video/RCTPlayerDelegate.swift b/ios/Video/RCTPlayerDelegate.swift new file mode 100644 index 0000000000..c1d386af4a --- /dev/null +++ b/ios/Video/RCTPlayerDelegate.swift @@ -0,0 +1,50 @@ +// +// RCTPlayerDelegate.swift +// +import UIKit +import AVKit +import AVFoundation + +@available(iOS 9.0, *) +class RCTPlayerDelegate: NSObject, AVPlayerViewControllerDelegate { + + private var _onPictureInPictureStatusChanged: RCTDirectEventBlock? + private var _onVideoFullscreenPlayerWillPresent: RCTDirectEventBlock? + private var _onVideoFullscreenPlayerWillDismiss: RCTDirectEventBlock? + private var _onVideoFullscreenPlayerDidDismiss: RCTDirectEventBlock? + + init(_ onPictureInPictureStatusChanged: RCTDirectEventBlock?, _ onVideoFullscreenPlayerWillPresent: RCTDirectEventBlock?, _ onVideoFullscreenPlayerWillDismiss: RCTDirectEventBlock?, _ onVideoFullscreenPlayerDidDismiss: RCTDirectEventBlock?) { + _onPictureInPictureStatusChanged = onPictureInPictureStatusChanged + _onVideoFullscreenPlayerWillPresent = onVideoFullscreenPlayerWillPresent + _onVideoFullscreenPlayerWillDismiss = onVideoFullscreenPlayerWillDismiss + _onVideoFullscreenPlayerDidDismiss = onVideoFullscreenPlayerDidDismiss + } + + func playerViewControllerDidStartPictureInPicture(_ playerViewController: AVPlayerViewController) { + + _onPictureInPictureStatusChanged!([ "isActive": NSNumber(value: true) ]) + + } + + func playerViewControllerDidStopPictureInPicture(_ playerViewController: AVPlayerViewController) { + + _onPictureInPictureStatusChanged!([ "isActive": NSNumber(value: false) ]) + + } + + func playerViewController(_ playerViewController: AVPlayerViewController, willBeginFullScreenPresentationWithAnimationCoordinator: UIViewControllerTransitionCoordinator) { + + _onVideoFullscreenPlayerWillPresent?([:]) + + } + + func playerViewController(_ playerViewController: AVPlayerViewController, willEndFullScreenPresentationWithAnimationCoordinator coordinator: UIViewControllerTransitionCoordinator) { + coordinator.animate(alongsideTransition: nil, completion: { _ in + self._onVideoFullscreenPlayerDidDismiss?([:]) + }) + + _onVideoFullscreenPlayerWillDismiss?([:]) + + } + +} \ No newline at end of file diff --git a/ios/Video/RCTVideo.swift b/ios/Video/RCTVideo.swift index 7518751ffa..3b6575b6ed 100644 --- a/ios/Video/RCTVideo.swift +++ b/ios/Video/RCTVideo.swift @@ -26,7 +26,7 @@ class RCTVideo: UIView, RCTVideoPlayerViewControllerDelegate, RCTPlayerObserverH /* Required to publish events */ private var _eventDispatcher:RCTEventDispatcher? - private var _videoLoadStarted:Bool = false +private var _videoLoadStarted:Bool = false private var _pendingSeek:Bool = false private var _pendingSeekTime:Float = 0.0 @@ -174,11 +174,6 @@ class RCTVideo: UIView, RCTVideoPlayerViewControllerDelegate, RCTPlayerObserverH } @objc func applicationDidEnterBackground(notification:NSNotification!) { - if _playInBackground { - // Needed to play sound in background. See https://developer.apple.com/library/ios/qa/qa1668/_index.html - _playerLayer?.player = nil - _playerViewController?.player = nil - } } @objc func applicationWillEnterForeground(notification:NSNotification!) { @@ -659,10 +654,14 @@ class RCTVideo: UIView, RCTVideoPlayerViewControllerDelegate, RCTPlayerObserverH } if viewController != nil { _presentingViewController = viewController - - self.onVideoFullscreenPlayerWillPresent?(["target": reactTag as Any]) + // Moved to RCTPlayerDelegate + // self.onVideoFullscreenPlayerWillPresent?(["target": reactTag as Any]) if let playerViewController = _playerViewController { + if(_controls) { + // prevents crash https://github.com/react-native-video/react-native-video/issues/3040 + self._playerViewController?.removeFromParent() + } viewController.present(playerViewController, animated:true, completion:{ self._playerViewController?.showsPlaybackControls = self._controls self._fullscreenPlayerPresented = fullscreen @@ -723,6 +722,7 @@ class RCTVideo: UIView, RCTVideoPlayerViewControllerDelegate, RCTPlayerObserverH viewController.showsPlaybackControls = self._controls viewController.rctDelegate = self viewController.preferredOrientation = _fullscreenOrientation + viewController.onVideoPlayerOrientationChange = self.onVideoPlayerOrientationChange viewController.view.frame = self.bounds viewController.player = player @@ -759,6 +759,10 @@ class RCTVideo: UIView, RCTVideoPlayerViewControllerDelegate, RCTPlayerObserverH { self.removePlayerLayer() self.usePlayerViewController() + if (_playerViewController !== nil) { + _playerDelegate = RCTPlayerDelegate(self.onPictureInPictureStatusChanged, self.onVideoFullscreenPlayerWillPresent, self.onVideoFullscreenPlayerWillDismiss, self.onVideoFullscreenPlayerDidDismiss) + _playerViewController!.delegate = _playerDelegate + } } else { @@ -787,7 +791,8 @@ class RCTVideo: UIView, RCTVideoPlayerViewControllerDelegate, RCTPlayerObserverH func videoPlayerViewControllerWillDismiss(playerViewController:AVPlayerViewController) { if _playerViewController == playerViewController && _fullscreenPlayerPresented, let onVideoFullscreenPlayerWillDismiss = onVideoFullscreenPlayerWillDismiss { _playerObserver.removePlayerViewControllerObservers() - onVideoFullscreenPlayerWillDismiss(["target": reactTag as Any]) + // Moved to RCTPlayerDelegate + // onVideoFullscreenPlayerWillDismiss(["target": reactTag as Any]) } } @@ -799,8 +804,8 @@ class RCTVideo: UIView, RCTVideoPlayerViewControllerDelegate, RCTPlayerObserverH _playerViewController = nil _playerObserver.playerViewController = nil self.applyModifiers() - - onVideoFullscreenPlayerDidDismiss?(["target": reactTag as Any]) + // Moved to RCTPlayerDelegate + // onVideoFullscreenPlayerDidDismiss?(["target": reactTag as Any]) } } diff --git a/ios/Video/RCTVideoManager.m b/ios/Video/RCTVideoManager.m index 0d503d1419..e1cea00034 100644 --- a/ios/Video/RCTVideoManager.m +++ b/ios/Video/RCTVideoManager.m @@ -51,6 +51,7 @@ @interface RCT_EXTERN_MODULE(RCTVideoManager, RCTViewManager) RCT_EXPORT_VIEW_PROPERTY(onVideoFullscreenPlayerDidPresent, RCTDirectEventBlock); RCT_EXPORT_VIEW_PROPERTY(onVideoFullscreenPlayerWillDismiss, RCTDirectEventBlock); RCT_EXPORT_VIEW_PROPERTY(onVideoFullscreenPlayerDidDismiss, RCTDirectEventBlock); +RCT_EXPORT_VIEW_PROPERTY(onVideoPlayerOrientationChange, RCTDirectEventBlock); RCT_EXPORT_VIEW_PROPERTY(onReadyForDisplay, RCTDirectEventBlock); RCT_EXPORT_VIEW_PROPERTY(onPlaybackStalled, RCTDirectEventBlock); RCT_EXPORT_VIEW_PROPERTY(onPlaybackResume, RCTDirectEventBlock); diff --git a/ios/Video/RCTVideoPlayerViewController.swift b/ios/Video/RCTVideoPlayerViewController.swift index e398e62f05..3bcbdfd74d 100644 --- a/ios/Video/RCTVideoPlayerViewController.swift +++ b/ios/Video/RCTVideoPlayerViewController.swift @@ -7,6 +7,7 @@ class RCTVideoPlayerViewController: AVPlayerViewController { // Optional paramters var preferredOrientation:String? var autorotate:Bool? + var onVideoPlayerOrientationChange: RCTDirectEventBlock? func shouldAutorotate() -> Bool { @@ -17,6 +18,15 @@ class RCTVideoPlayerViewController: AVPlayerViewController { return false } + override func viewDidLoad() { + super.viewDidLoad() + NotificationCenter.default.addObserver(self, selector: #selector(deviceOrientationDidChange), name: UIDevice.orientationDidChangeNotification, object: nil) + } + + deinit { + NotificationCenter.default.removeObserver(self, name: UIDevice.orientationDidChangeNotification, object: nil) + } + override func viewDidDisappear(_ animated: Bool) { super.viewDidDisappear(animated) @@ -28,6 +38,25 @@ class RCTVideoPlayerViewController: AVPlayerViewController { #if !TARGET_OS_TV + @objc func deviceOrientationDidChange() { + let orientation = UIDevice.current.orientation + // Handle device orientation change + // Numbers refer to Expo Scree Orientation types: + // https://docs.expo.dev/versions/latest/sdk/screen-orientation#orientation + switch orientation { + case .portrait: + onVideoPlayerOrientationChange!([ "orientation": NSNumber(value: 1) ]) + case .portraitUpsideDown: + onVideoPlayerOrientationChange!([ "orientation": NSNumber(value: 2) ]) + case .landscapeLeft: + onVideoPlayerOrientationChange!([ "orientation": NSNumber(value: 3) ]) + case .landscapeRight: + onVideoPlayerOrientationChange!([ "orientation": NSNumber(value: 4) ]) + default: + onVideoPlayerOrientationChange!([ "orientation": NSNumber(value: 0) ]) + } + } + func supportedInterfaceOrientations() -> UIInterfaceOrientationMask { return .all }