react-native-video/ios/Video/RCTVideoManager.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

114 lines
4.0 KiB
Swift

import AVFoundation
import React
@objc(RCTVideoManager)
class RCTVideoManager: RCTViewManager {
override func view() -> UIView {
return RCTVideo(eventDispatcher: (RCTBridge.current().eventDispatcher() as! RCTEventDispatcher))
}
func methodQueue() -> DispatchQueue {
return bridge.uiManager.methodQueue
}
func performOnVideoView(withReactTag reactTag: NSNumber, callback: @escaping (RCTVideo?) -> Void) {
DispatchQueue.main.async { [weak self] in
guard let self else {
callback(nil)
return
}
let view = self.bridge.uiManager.view(forReactTag: reactTag)
guard let videoView = view as? RCTVideo else {
DebugLog("Invalid view returned from registry, expecting RCTVideo, got: \(String(describing: self.view))")
callback(nil)
return
}
callback(videoView)
}
}
@objc(seekCmd:time:tolerance:)
func seekCmd(_ reactTag: NSNumber, time: NSNumber, tolerance: NSNumber) {
performOnVideoView(withReactTag: reactTag, callback: { videoView in
videoView?.setSeek(time, tolerance)
})
}
@objc(setLicenseResultCmd:license:licenseUrl:)
func setLicenseResultCmd(_ reactTag: NSNumber, license: NSString, licenseUrl: NSString) {
performOnVideoView(withReactTag: reactTag, callback: { videoView in
videoView?.setLicenseResult(license as String, licenseUrl as String)
})
}
@objc(setLicenseResultErrorCmd:error:licenseUrl:)
func setLicenseResultErrorCmd(_ reactTag: NSNumber, error: NSString, licenseUrl: NSString) {
performOnVideoView(withReactTag: reactTag, callback: { videoView in
videoView?.setLicenseResultError(error as String, licenseUrl as String)
})
}
@objc(setPlayerPauseStateCmd:paused:)
func setPlayerPauseStateCmd(_ reactTag: NSNumber, paused: Bool) {
performOnVideoView(withReactTag: reactTag, callback: { videoView in
videoView?.setPaused(paused)
})
}
@objc(setVolumeCmd:volume:)
func setVolumeCmd(_ reactTag: NSNumber, volume: Float) {
performOnVideoView(withReactTag: reactTag, callback: { videoView in
videoView?.setVolume(volume)
})
}
@objc(setFullScreenCmd:fullscreen:)
func setFullScreenCmd(_ reactTag: NSNumber, fullScreen: Bool) {
performOnVideoView(withReactTag: reactTag, callback: { videoView in
videoView?.setFullscreen(fullScreen)
})
}
@objc(enterPictureInPictureCmd:)
func enterPictureInPictureCmd(_ reactTag: NSNumber) {
performOnVideoView(withReactTag: reactTag, callback: { videoView in
videoView?.enterPictureInPicture()
})
}
@objc(exitPictureInPictureCmd:)
func exitPictureInPictureCmd(_ reactTag: NSNumber) {
performOnVideoView(withReactTag: reactTag, callback: { videoView in
videoView?.exitPictureInPicture()
})
}
@objc(setSourceCmd:source:)
func setSourceCmd(_ reactTag: NSNumber, source: NSDictionary) {
performOnVideoView(withReactTag: reactTag, callback: { videoView in
videoView?.setSrc(source)
})
}
@objc(save:options:resolve:reject:)
func save(_ reactTag: NSNumber, options: NSDictionary, resolve: @escaping RCTPromiseResolveBlock, reject: @escaping RCTPromiseRejectBlock) {
performOnVideoView(withReactTag: reactTag, callback: { videoView in
videoView?.save(options, resolve, reject)
})
}
@objc(getCurrentPosition:resolve:reject:)
func getCurrentPosition(_ reactTag: NSNumber, resolve: @escaping RCTPromiseResolveBlock, reject: @escaping RCTPromiseRejectBlock) {
performOnVideoView(withReactTag: reactTag, callback: { videoView in
videoView?.getCurrentPlaybackTime(resolve, reject)
})
}
override class func requiresMainQueueSetup() -> Bool {
return true
}
}