react-native-video/ios/Video/Features/RCTPictureInPicture.swift
YangJH 69a7bc2d26
feat: implement enterPictureInPictureOnLeave prop for both platform (Android, iOS) (#3385)
* docs: enable Android PIP

* chore: change comments

* feat(android): implement Android PictureInPicture

* refactor: minor refactor code and apply lint

* fix: rewrite pip action intent code for Android14

* fix: remove redundant codes

* feat: add isInPictureInPicture flag for lifecycle handling

- activity provide helper method for same purpose, but this flag makes code simple

* feat: add pipFullscreenPlayerView for makes PIP include video only

* fix: add manifest value checker for prevent crash

* docs: add pictureInPicture prop's Android guide

* fix: sync controller visibility

* refactor: refining variable name

* fix: check multi window mode when host pause

- some OS version call onPause on multi-window mode

* fix: handling when onStop is called while in multi-window mode

* refactor:  enhance PIP util codes

* fix: fix FullscreenPlayerView constructor

* refactor: add enterPictureInPictureOnLeave prop and pip methods

- remove pictureInPicture boolean prop
- add enterPictureInPictureOnLeave boolean prop
- add enterPictureInPicture method
- add exitPictureInPicture method

* fix: fix lint error

* fix: prevent audio play in background without playInBackground prop

* fix: fix onDetachedFromWindow

* docs: update docs for pip

* fix(android): sync pip controller with external controller state

- for media session

* fix(ios): fix pip active fn variable reference

* refactor(ios): refactor code

* refactor(android): refactor codes

* fix(android): fix lint error

* refactor(android): refactor android pip logics

* fix(android): fix flickering issue when stop picture in picture

* fix(android): fix import

* fix(android): fix picture in picture with fullscreen mode

* fix(ios): fix syntax error

* fix(android): fix Fragment managed code

* refactor(android): remove redundant override lifecycle

* fix(js): add PIP type definition for codegen

* fix(android): fix syntax

* chore(android): fix lint error

* fix(ios): fix enter background handler

* refactor(ios): remove redundant code

* fix(ios): fix applicationDidEnterBackground for PIP

* fix(android): fix onPictureInPictureStatusChanged

* fix(ios): fix RCTPictureInPicture

* refactor(android): Ignore exception for some device ignore pip checker

- some device ignore PIP availability check, so we need to handle exception to prevent crash

* fix(android): add hideWithoutPlayer fn into Kotlin ver

* refactor(android): remove redundant code

* fix(android): fix pip ratio to be calculated with correct ratio value

* fix(android): fix crash issue when unmounting in PIP mode

* fix(android): fix lint error

* Update android/src/main/java/com/brentvatne/react/VideoManagerModule.kt

* fix(android): fix lint error

* fix(ios): fix lint error

* fix(ios): fix lint error

* feat(expo): add android picture in picture config within expo plugin

* fix: Replace Fragment with androidx.activity

- remove code that uses Fragment, which is a tricky implementation

* fix: fix lint error

* fix(android): disable auto enter when player released

* fix(android): fix event handler to check based on Activity it's bound to

---------

Co-authored-by: jonghun <jonghun@toss.im>
Co-authored-by: Olivier Bouillet <62574056+freeboub@users.noreply.github.com>
2025-01-04 12:37:33 +01:00

94 lines
3.7 KiB
Swift

import AVFoundation
import AVKit
import Foundation
import MediaAccessibility
import React
#if os(iOS)
class RCTPictureInPicture: NSObject, AVPictureInPictureControllerDelegate {
public private(set) var _pipController: AVPictureInPictureController?
private var _onPictureInPictureEnter: (() -> Void)?
private var _onPictureInPictureExit: (() -> Void)?
private var _onRestoreUserInterfaceForPictureInPictureStop: (() -> Void)?
private var _restoreUserInterfaceForPIPStopCompletionHandler: ((Bool) -> Void)?
private var _isPictureInPictureActive: Bool {
return _pipController?.isPictureInPictureActive ?? false
}
init(
_ onPictureInPictureEnter: (() -> Void)? = nil,
_ onPictureInPictureExit: (() -> Void)? = nil,
_ onRestoreUserInterfaceForPictureInPictureStop: (() -> Void)? = nil
) {
_onPictureInPictureEnter = onPictureInPictureEnter
_onPictureInPictureExit = onPictureInPictureExit
_onRestoreUserInterfaceForPictureInPictureStop = onRestoreUserInterfaceForPictureInPictureStop
}
func pictureInPictureControllerDidStartPictureInPicture(_: AVPictureInPictureController) {
guard let _onPictureInPictureEnter else { return }
_onPictureInPictureEnter()
}
func pictureInPictureControllerDidStopPictureInPicture(_: AVPictureInPictureController) {
guard let _onPictureInPictureExit else { return }
_onPictureInPictureExit()
}
func pictureInPictureController(
_: AVPictureInPictureController,
restoreUserInterfaceForPictureInPictureStopWithCompletionHandler completionHandler: @escaping (Bool) -> Void
) {
guard let _onRestoreUserInterfaceForPictureInPictureStop else { return }
_onRestoreUserInterfaceForPictureInPictureStop()
_restoreUserInterfaceForPIPStopCompletionHandler = completionHandler
}
func setRestoreUserInterfaceForPIPStopCompletionHandler(_ restore: Bool) {
guard let _restoreUserInterfaceForPIPStopCompletionHandler else { return }
_restoreUserInterfaceForPIPStopCompletionHandler(restore)
self._restoreUserInterfaceForPIPStopCompletionHandler = nil
}
func setupPipController(_ playerLayer: AVPlayerLayer?) {
guard let playerLayer else { return }
if !AVPictureInPictureController.isPictureInPictureSupported() { return }
// Create new controller passing reference to the AVPlayerLayer
_pipController = AVPictureInPictureController(playerLayer: playerLayer)
if #available(iOS 14.2, *) {
_pipController?.canStartPictureInPictureAutomaticallyFromInline = true
}
_pipController?.delegate = self
}
func deinitPipController() {
_pipController = nil
}
func enterPictureInPicture() {
guard let _pipController else { return }
if !_isPictureInPictureActive {
_pipController.startPictureInPicture()
}
}
func exitPictureInPicture() {
guard let _pipController else { return }
if _isPictureInPictureActive {
let state = UIApplication.shared.applicationState
if state == .background || state == .inactive {
deinitPipController()
_onPictureInPictureExit?()
_onRestoreUserInterfaceForPictureInPictureStop?()
} else {
_pipController.stopPictureInPicture()
}
}
}
}
#endif