2022-05-19 07:29:25 -06:00
|
|
|
import AVFoundation
|
|
|
|
import AVKit
|
|
|
|
import Foundation
|
2023-03-12 16:29:03 -06:00
|
|
|
#if USE_GOOGLE_IMA
|
2023-12-07 00:47:40 -07:00
|
|
|
import GoogleInteractiveMediaAds
|
2023-03-12 16:29:03 -06:00
|
|
|
#endif
|
2023-12-07 00:47:40 -07:00
|
|
|
import React
|
2022-05-19 07:29:25 -06:00
|
|
|
|
2023-12-07 00:47:40 -07:00
|
|
|
// MARK: - RCTVideo
|
2022-05-19 07:29:25 -06:00
|
|
|
|
2023-12-07 00:47:40 -07:00
|
|
|
class RCTVideo: UIView, RCTVideoPlayerViewControllerDelegate, RCTPlayerObserverHandler {
|
|
|
|
private var _player: AVPlayer?
|
|
|
|
private var _playerItem: AVPlayerItem?
|
|
|
|
private var _source: VideoSource?
|
|
|
|
private var _playerLayer: AVPlayerLayer?
|
|
|
|
private var _chapters: [Chapter]?
|
2022-11-09 09:26:12 -07:00
|
|
|
|
2023-12-07 00:47:40 -07:00
|
|
|
private var _playerViewController: RCTVideoPlayerViewController?
|
|
|
|
private var _videoURL: NSURL?
|
|
|
|
private var _localSourceEncryptionKeyScheme: String?
|
2022-11-09 09:26:12 -07:00
|
|
|
|
2022-05-19 07:29:25 -06:00
|
|
|
/* Required to publish events */
|
2023-12-07 00:47:40 -07:00
|
|
|
private var _eventDispatcher: RCTEventDispatcher?
|
|
|
|
private var _videoLoadStarted = false
|
2022-11-09 09:26:12 -07:00
|
|
|
|
2023-12-07 00:47:40 -07:00
|
|
|
private var _pendingSeek = false
|
|
|
|
private var _pendingSeekTime: Float = 0.0
|
|
|
|
private var _lastSeekTime: Float = 0.0
|
2022-11-09 09:26:12 -07:00
|
|
|
|
2022-05-19 07:29:25 -06:00
|
|
|
/* For sending videoProgress events */
|
2023-12-07 00:47:40 -07:00
|
|
|
private var _controls = false
|
2022-11-09 09:26:12 -07:00
|
|
|
|
2022-05-19 07:29:25 -06:00
|
|
|
/* Keep track of any modifiers, need to be applied after each play */
|
2023-04-02 12:02:56 -06:00
|
|
|
private var _audioOutput: String = "speaker"
|
2023-12-07 00:47:40 -07:00
|
|
|
private var _volume: Float = 1.0
|
|
|
|
private var _rate: Float = 1.0
|
|
|
|
private var _maxBitRate: Float?
|
|
|
|
|
|
|
|
private var _automaticallyWaitsToMinimizeStalling = true
|
|
|
|
private var _muted = false
|
|
|
|
private var _paused = false
|
|
|
|
private var _repeat = false
|
2024-05-28 13:04:55 -06:00
|
|
|
private var _isPlaying = false
|
2023-12-07 00:47:40 -07:00
|
|
|
private var _allowsExternalPlayback = true
|
2024-09-02 07:40:38 -06:00
|
|
|
private var _textTracks: [TextTrack] = []
|
2023-12-07 00:47:40 -07:00
|
|
|
private var _selectedTextTrackCriteria: SelectedTrackCriteria?
|
|
|
|
private var _selectedAudioTrackCriteria: SelectedTrackCriteria?
|
|
|
|
private var _playbackStalled = false
|
|
|
|
private var _playInBackground = false
|
|
|
|
private var _preventsDisplaySleepDuringVideoPlayback = true
|
|
|
|
private var _preferredForwardBufferDuration: Float = 0.0
|
|
|
|
private var _playWhenInactive = false
|
|
|
|
private var _ignoreSilentSwitch: String! = "inherit" // inherit, ignore, obey
|
|
|
|
private var _mixWithOthers: String! = "inherit" // inherit, mix, duck
|
|
|
|
private var _resizeMode: String! = "cover"
|
|
|
|
private var _fullscreen = false
|
|
|
|
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?
|
|
|
|
private var _startPosition: Float64 = -1
|
2024-05-07 04:30:57 -06:00
|
|
|
private var _showNotificationControls = false
|
2024-04-05 02:37:00 -06:00
|
|
|
private var _pictureInPictureEnabled = false {
|
|
|
|
didSet {
|
|
|
|
#if os(iOS)
|
|
|
|
if _pictureInPictureEnabled {
|
|
|
|
initPictureinPicture()
|
|
|
|
_playerViewController?.allowsPictureInPicturePlayback = true
|
|
|
|
} else {
|
|
|
|
_pip?.deinitPipController()
|
|
|
|
_playerViewController?.allowsPictureInPicturePlayback = false
|
|
|
|
}
|
|
|
|
#endif
|
|
|
|
}
|
|
|
|
}
|
2022-11-17 04:01:29 -07:00
|
|
|
|
2024-06-25 00:55:32 -06:00
|
|
|
private let instanceId = UUID().uuidString
|
|
|
|
|
2024-05-13 13:32:51 -06:00
|
|
|
private var _isBuffering = false {
|
|
|
|
didSet {
|
|
|
|
onVideoBuffer?(["isBuffering": _isBuffering, "target": reactTag as Any])
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-11-17 04:01:29 -07:00
|
|
|
/* IMA Ads */
|
2023-12-07 00:47:40 -07:00
|
|
|
private var _adTagUrl: String?
|
2024-08-29 04:30:05 -06:00
|
|
|
private var _adLanguage: String?
|
2023-12-07 00:47:40 -07:00
|
|
|
#if USE_GOOGLE_IMA
|
|
|
|
private var _imaAdsManager: RCTIMAAdsManager!
|
|
|
|
/* Playhead used by the SDK to track content video progress and insert mid-rolls. */
|
|
|
|
private var _contentPlayhead: IMAAVPlayerContentPlayhead?
|
|
|
|
#endif
|
|
|
|
private var _didRequestAds = false
|
|
|
|
private var _adPlaying = false
|
2022-11-09 09:26:12 -07:00
|
|
|
|
2022-05-19 07:29:25 -06:00
|
|
|
private var _resouceLoaderDelegate: RCTResourceLoaderDelegate?
|
2023-12-07 00:47:40 -07:00
|
|
|
private var _playerObserver: RCTPlayerObserver = .init()
|
2022-11-09 09:26:12 -07:00
|
|
|
|
2023-12-07 00:47:40 -07:00
|
|
|
#if USE_VIDEO_CACHING
|
|
|
|
private let _videoCache: RCTVideoCachingHandler = .init()
|
|
|
|
#endif
|
2022-11-09 09:26:12 -07:00
|
|
|
|
2023-12-07 00:47:40 -07:00
|
|
|
#if os(iOS)
|
|
|
|
private var _pip: RCTPictureInPicture?
|
|
|
|
#endif
|
2022-11-09 09:26:12 -07:00
|
|
|
|
2022-05-19 07:29:25 -06:00
|
|
|
// Events
|
|
|
|
@objc var onVideoLoadStart: RCTDirectEventBlock?
|
|
|
|
@objc var onVideoLoad: RCTDirectEventBlock?
|
|
|
|
@objc var onVideoBuffer: RCTDirectEventBlock?
|
|
|
|
@objc var onVideoError: RCTDirectEventBlock?
|
|
|
|
@objc var onVideoProgress: RCTDirectEventBlock?
|
2023-11-08 14:06:29 -07:00
|
|
|
@objc var onVideoBandwidthUpdate: RCTDirectEventBlock?
|
2022-05-19 07:29:25 -06:00
|
|
|
@objc var onVideoSeek: RCTDirectEventBlock?
|
|
|
|
@objc var onVideoEnd: RCTDirectEventBlock?
|
|
|
|
@objc var onTimedMetadata: RCTDirectEventBlock?
|
|
|
|
@objc var onVideoAudioBecomingNoisy: RCTDirectEventBlock?
|
|
|
|
@objc var onVideoFullscreenPlayerWillPresent: RCTDirectEventBlock?
|
|
|
|
@objc var onVideoFullscreenPlayerDidPresent: RCTDirectEventBlock?
|
|
|
|
@objc var onVideoFullscreenPlayerWillDismiss: RCTDirectEventBlock?
|
|
|
|
@objc var onVideoFullscreenPlayerDidDismiss: RCTDirectEventBlock?
|
|
|
|
@objc var onReadyForDisplay: RCTDirectEventBlock?
|
|
|
|
@objc var onPlaybackStalled: RCTDirectEventBlock?
|
|
|
|
@objc var onPlaybackResume: RCTDirectEventBlock?
|
|
|
|
@objc var onPlaybackRateChange: RCTDirectEventBlock?
|
2023-11-04 11:11:54 -06:00
|
|
|
@objc var onVolumeChange: RCTDirectEventBlock?
|
2023-10-23 10:23:57 -06:00
|
|
|
@objc var onVideoPlaybackStateChanged: RCTDirectEventBlock?
|
2022-05-19 07:29:25 -06:00
|
|
|
@objc var onVideoExternalPlaybackChange: RCTDirectEventBlock?
|
|
|
|
@objc var onPictureInPictureStatusChanged: RCTDirectEventBlock?
|
|
|
|
@objc var onRestoreUserInterfaceForPictureInPictureStop: RCTDirectEventBlock?
|
|
|
|
@objc var onGetLicense: RCTDirectEventBlock?
|
2022-11-10 03:43:50 -07:00
|
|
|
@objc var onReceiveAdEvent: RCTDirectEventBlock?
|
2024-02-01 00:56:00 -07:00
|
|
|
@objc var onTextTracks: RCTDirectEventBlock?
|
|
|
|
@objc var onAudioTracks: RCTDirectEventBlock?
|
2024-02-29 06:41:04 -07:00
|
|
|
@objc var onTextTrackDataChanged: RCTDirectEventBlock?
|
2023-11-17 00:19:39 -07:00
|
|
|
|
2023-12-07 00:47:40 -07:00
|
|
|
@objc
|
2024-03-26 07:10:31 -06:00
|
|
|
func _onPictureInPictureEnter() {
|
2023-12-07 00:47:40 -07:00
|
|
|
onPictureInPictureStatusChanged?(["isActive": NSNumber(value: true)])
|
2023-08-28 11:55:34 -06:00
|
|
|
}
|
|
|
|
|
2023-12-07 00:47:40 -07:00
|
|
|
@objc
|
2024-03-26 07:10:31 -06:00
|
|
|
func _onPictureInPictureExit() {
|
2023-12-07 00:47:40 -07:00
|
|
|
onPictureInPictureStatusChanged?(["isActive": NSNumber(value: false)])
|
2023-08-28 11:55:34 -06:00
|
|
|
}
|
2022-11-09 09:26:12 -07:00
|
|
|
|
2024-04-04 07:08:48 -06:00
|
|
|
func handlePictureInPictureEnter() {
|
|
|
|
onPictureInPictureStatusChanged?(["isActive": NSNumber(value: true)])
|
|
|
|
}
|
|
|
|
|
|
|
|
func handlePictureInPictureExit() {
|
|
|
|
onPictureInPictureStatusChanged?(["isActive": NSNumber(value: false)])
|
2024-07-08 05:41:21 -06:00
|
|
|
|
|
|
|
// To continue audio playback in backgroud we need to set
|
|
|
|
// player in _playerLayer & _playerViewController to nil
|
|
|
|
let appState = UIApplication.shared.applicationState
|
|
|
|
if _playInBackground && appState == .background {
|
|
|
|
_playerLayer?.player = nil
|
|
|
|
_playerViewController?.player = nil
|
|
|
|
_player?.play()
|
|
|
|
}
|
2024-04-04 07:08:48 -06:00
|
|
|
}
|
|
|
|
|
|
|
|
func handleRestoreUserInterfaceForPictureInPictureStop() {
|
|
|
|
onRestoreUserInterfaceForPictureInPictureStop?([:])
|
|
|
|
}
|
|
|
|
|
2023-12-07 00:47:40 -07:00
|
|
|
func isPipEnabled() -> Bool {
|
2023-09-17 13:12:46 -06:00
|
|
|
return _pictureInPictureEnabled
|
|
|
|
}
|
|
|
|
|
2024-07-11 04:54:54 -06:00
|
|
|
func isPipActive() -> Bool {
|
|
|
|
#if os(iOS)
|
|
|
|
return _pip?._pipController?.isPictureInPictureActive == true
|
|
|
|
#else
|
|
|
|
return false
|
|
|
|
#endif
|
|
|
|
}
|
|
|
|
|
2024-04-05 02:37:00 -06:00
|
|
|
func initPictureinPicture() {
|
|
|
|
#if os(iOS)
|
|
|
|
_pip = RCTPictureInPicture({ [weak self] in
|
|
|
|
self?._onPictureInPictureEnter()
|
|
|
|
}, { [weak self] in
|
|
|
|
self?._onPictureInPictureExit()
|
|
|
|
}, { [weak self] in
|
|
|
|
self?.onRestoreUserInterfaceForPictureInPictureStop?([:])
|
|
|
|
})
|
|
|
|
|
|
|
|
if _playerLayer != nil && !_controls {
|
|
|
|
_pip?.setupPipController(_playerLayer)
|
|
|
|
}
|
|
|
|
#else
|
|
|
|
DebugLog("Picture in Picture is not supported on this platform")
|
|
|
|
#endif
|
|
|
|
}
|
|
|
|
|
2023-12-07 00:47:40 -07:00
|
|
|
init(eventDispatcher: RCTEventDispatcher!) {
|
2022-05-19 07:29:25 -06:00
|
|
|
super.init(frame: CGRect(x: 0, y: 0, width: 100, height: 100))
|
2024-06-25 00:55:32 -06:00
|
|
|
ReactNativeVideoManager.shared.registerView(newInstance: self)
|
2023-12-07 00:47:40 -07:00
|
|
|
#if USE_GOOGLE_IMA
|
|
|
|
_imaAdsManager = RCTIMAAdsManager(video: self, pipEnabled: isPipEnabled)
|
|
|
|
#endif
|
2022-11-17 04:01:29 -07:00
|
|
|
|
2022-05-19 07:29:25 -06:00
|
|
|
_eventDispatcher = eventDispatcher
|
2022-11-09 09:26:12 -07:00
|
|
|
|
2023-12-07 00:47:40 -07:00
|
|
|
#if os(iOS)
|
2024-04-05 02:37:00 -06:00
|
|
|
if _pictureInPictureEnabled {
|
|
|
|
initPictureinPicture()
|
|
|
|
_playerViewController?.allowsPictureInPicturePlayback = true
|
|
|
|
} else {
|
|
|
|
_playerViewController?.allowsPictureInPicturePlayback = false
|
|
|
|
}
|
2023-12-07 00:47:40 -07:00
|
|
|
#endif
|
2023-08-28 11:55:34 -06:00
|
|
|
|
2022-05-19 07:29:25 -06:00
|
|
|
NotificationCenter.default.addObserver(
|
|
|
|
self,
|
|
|
|
selector: #selector(applicationWillResignActive(notification:)),
|
|
|
|
name: UIApplication.willResignActiveNotification,
|
|
|
|
object: nil
|
|
|
|
)
|
2023-12-02 05:52:01 -07:00
|
|
|
|
2023-11-06 02:20:42 -07:00
|
|
|
NotificationCenter.default.addObserver(
|
|
|
|
self,
|
|
|
|
selector: #selector(applicationDidBecomeActive(notification:)),
|
|
|
|
name: UIApplication.didBecomeActiveNotification,
|
|
|
|
object: nil
|
|
|
|
)
|
2022-11-09 09:26:12 -07:00
|
|
|
|
2022-05-19 07:29:25 -06:00
|
|
|
NotificationCenter.default.addObserver(
|
|
|
|
self,
|
|
|
|
selector: #selector(applicationDidEnterBackground(notification:)),
|
|
|
|
name: UIApplication.didEnterBackgroundNotification,
|
|
|
|
object: nil
|
|
|
|
)
|
2022-11-09 09:26:12 -07:00
|
|
|
|
2022-05-19 07:29:25 -06:00
|
|
|
NotificationCenter.default.addObserver(
|
|
|
|
self,
|
|
|
|
selector: #selector(applicationWillEnterForeground(notification:)),
|
|
|
|
name: UIApplication.willEnterForegroundNotification,
|
|
|
|
object: nil
|
|
|
|
)
|
2022-11-09 09:26:12 -07:00
|
|
|
|
2022-05-19 07:29:25 -06:00
|
|
|
NotificationCenter.default.addObserver(
|
|
|
|
self,
|
|
|
|
selector: #selector(audioRouteChanged(notification:)),
|
|
|
|
name: AVAudioSession.routeChangeNotification,
|
|
|
|
object: nil
|
|
|
|
)
|
|
|
|
_playerObserver._handlers = self
|
2023-12-07 00:47:40 -07:00
|
|
|
#if USE_VIDEO_CACHING
|
|
|
|
_videoCache.playerItemPrepareText = playerItemPrepareText
|
|
|
|
#endif
|
2022-05-19 07:29:25 -06:00
|
|
|
}
|
2022-11-09 09:26:12 -07:00
|
|
|
|
2022-05-19 07:29:25 -06:00
|
|
|
required init?(coder aDecoder: NSCoder) {
|
|
|
|
super.init(coder: aDecoder)
|
2023-12-07 00:47:40 -07:00
|
|
|
#if USE_GOOGLE_IMA
|
|
|
|
_imaAdsManager = RCTIMAAdsManager(video: self, pipEnabled: isPipEnabled)
|
|
|
|
#endif
|
2022-05-19 07:29:25 -06:00
|
|
|
}
|
2022-11-09 09:26:12 -07:00
|
|
|
|
2022-05-19 07:29:25 -06:00
|
|
|
deinit {
|
2024-06-20 03:18:27 -06:00
|
|
|
#if USE_GOOGLE_IMA
|
|
|
|
_imaAdsManager.releaseAds()
|
|
|
|
_imaAdsManager = nil
|
|
|
|
#endif
|
|
|
|
|
2022-05-19 07:29:25 -06:00
|
|
|
NotificationCenter.default.removeObserver(self)
|
|
|
|
self.removePlayerLayer()
|
|
|
|
_playerObserver.clearPlayer()
|
2024-02-08 06:59:13 -07:00
|
|
|
|
2024-05-07 04:30:57 -06:00
|
|
|
if let player = _player {
|
|
|
|
NowPlayingInfoCenterManager.shared.removePlayer(player: player)
|
|
|
|
}
|
|
|
|
|
2024-02-08 06:59:13 -07:00
|
|
|
#if os(iOS)
|
|
|
|
_pip = nil
|
|
|
|
#endif
|
2024-06-25 00:55:32 -06:00
|
|
|
ReactNativeVideoManager.shared.unregisterView(newInstance: self)
|
2022-05-19 07:29:25 -06:00
|
|
|
}
|
2022-11-09 09:26:12 -07:00
|
|
|
|
2022-05-19 07:29:25 -06:00
|
|
|
// MARK: - App lifecycle handlers
|
2022-11-09 09:26:12 -07:00
|
|
|
|
2024-08-29 04:27:07 -06:00
|
|
|
func getIsExternalPlaybackActive() -> Bool {
|
|
|
|
#if os(visionOS)
|
|
|
|
let isExternalPlaybackActive = false
|
|
|
|
#else
|
|
|
|
let isExternalPlaybackActive = _player?.isExternalPlaybackActive ?? false
|
|
|
|
#endif
|
|
|
|
return isExternalPlaybackActive
|
|
|
|
}
|
|
|
|
|
2023-12-07 00:47:40 -07:00
|
|
|
@objc
|
|
|
|
func applicationWillResignActive(notification _: NSNotification!) {
|
2024-08-29 04:27:07 -06:00
|
|
|
let isExternalPlaybackActive = getIsExternalPlaybackActive()
|
2024-05-28 13:04:55 -06:00
|
|
|
if _playInBackground || _playWhenInactive || !_isPlaying || isExternalPlaybackActive { return }
|
2022-11-09 09:26:12 -07:00
|
|
|
|
2022-05-19 07:29:25 -06:00
|
|
|
_player?.pause()
|
|
|
|
_player?.rate = 0.0
|
|
|
|
}
|
2023-11-17 00:19:39 -07:00
|
|
|
|
2023-12-07 00:47:40 -07:00
|
|
|
@objc
|
|
|
|
func applicationDidBecomeActive(notification _: NSNotification!) {
|
2024-08-29 04:27:07 -06:00
|
|
|
let isExternalPlaybackActive = getIsExternalPlaybackActive()
|
2024-05-28 13:04:55 -06:00
|
|
|
if _playInBackground || _playWhenInactive || !_isPlaying || isExternalPlaybackActive { return }
|
2023-11-06 02:20:42 -07:00
|
|
|
|
|
|
|
// Resume the player or any other tasks that should continue when the app becomes active.
|
|
|
|
_player?.play()
|
|
|
|
_player?.rate = _rate
|
|
|
|
}
|
2022-11-09 09:26:12 -07:00
|
|
|
|
2023-12-07 00:47:40 -07:00
|
|
|
@objc
|
|
|
|
func applicationDidEnterBackground(notification _: NSNotification!) {
|
2024-08-29 04:27:07 -06:00
|
|
|
let isExternalPlaybackActive = getIsExternalPlaybackActive()
|
2024-07-11 04:54:54 -06:00
|
|
|
if !_playInBackground || isExternalPlaybackActive || isPipActive() { return }
|
2024-05-28 13:04:55 -06:00
|
|
|
// Needed to play sound in background. See https://developer.apple.com/library/ios/qa/qa1668/_index.html
|
|
|
|
_playerLayer?.player = nil
|
|
|
|
_playerViewController?.player = nil
|
2022-05-19 07:29:25 -06:00
|
|
|
}
|
2022-11-09 09:26:12 -07:00
|
|
|
|
2023-12-07 00:47:40 -07:00
|
|
|
@objc
|
|
|
|
func applicationWillEnterForeground(notification _: NSNotification!) {
|
2022-05-19 07:29:25 -06:00
|
|
|
self.applyModifiers()
|
2024-05-28 13:04:55 -06:00
|
|
|
_playerLayer?.player = _player
|
|
|
|
_playerViewController?.player = _player
|
2022-05-19 07:29:25 -06:00
|
|
|
}
|
2022-11-09 09:26:12 -07:00
|
|
|
|
2022-05-19 07:29:25 -06:00
|
|
|
// MARK: - Audio events
|
2022-11-09 09:26:12 -07:00
|
|
|
|
2023-12-07 00:47:40 -07:00
|
|
|
@objc
|
|
|
|
func audioRouteChanged(notification: NSNotification!) {
|
2022-05-19 07:29:25 -06:00
|
|
|
if let userInfo = notification.userInfo {
|
2023-12-07 00:47:40 -07:00
|
|
|
let reason: AVAudioSession.RouteChangeReason! = userInfo[AVAudioSessionRouteChangeReasonKey] as? AVAudioSession.RouteChangeReason
|
2022-05-19 07:29:25 -06:00
|
|
|
// let previousRoute:NSNumber! = userInfo[AVAudioSessionRouteChangePreviousRouteKey] as? NSNumber
|
2024-01-04 12:16:23 -07:00
|
|
|
if reason == .oldDeviceUnavailable, let onVideoAudioBecomingNoisy {
|
2022-05-19 07:29:25 -06:00
|
|
|
onVideoAudioBecomingNoisy(["target": reactTag as Any])
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2022-11-09 09:26:12 -07:00
|
|
|
|
2022-05-19 07:29:25 -06:00
|
|
|
// MARK: - Progress
|
2022-11-09 09:26:12 -07:00
|
|
|
|
2024-06-03 04:13:52 -06:00
|
|
|
func sendProgressUpdate(didEnd: Bool = false) {
|
2024-05-08 08:07:32 -06:00
|
|
|
#if !USE_GOOGLE_IMA
|
|
|
|
// If we dont use Ads and onVideoProgress is not defined we dont need to run this code
|
|
|
|
guard onVideoProgress != nil else { return }
|
|
|
|
#endif
|
|
|
|
|
2022-05-19 07:29:25 -06:00
|
|
|
if let video = _player?.currentItem,
|
|
|
|
video == nil || video.status != AVPlayerItem.Status.readyToPlay {
|
|
|
|
return
|
|
|
|
}
|
2022-11-09 09:26:12 -07:00
|
|
|
|
2023-12-07 00:47:40 -07:00
|
|
|
let playerDuration: CMTime = RCTVideoUtils.playerItemDuration(_player)
|
2022-05-19 07:29:25 -06:00
|
|
|
if CMTIME_IS_INVALID(playerDuration) {
|
|
|
|
return
|
|
|
|
}
|
2022-11-09 09:26:12 -07:00
|
|
|
|
2023-02-07 13:50:54 -07:00
|
|
|
var currentTime = _player?.currentTime()
|
2023-12-07 00:47:40 -07:00
|
|
|
if currentTime != nil && _source?.cropStart != nil {
|
2023-11-24 04:52:46 -07:00
|
|
|
currentTime = CMTimeSubtract(currentTime!, CMTimeMake(value: _source?.cropStart ?? 0, timescale: 1000))
|
2023-02-07 13:50:54 -07:00
|
|
|
}
|
2022-05-19 07:29:25 -06:00
|
|
|
let currentPlaybackTime = _player?.currentItem?.currentDate()
|
|
|
|
let duration = CMTimeGetSeconds(playerDuration)
|
2024-06-03 04:13:52 -06:00
|
|
|
var currentTimeSecs = CMTimeGetSeconds(currentTime ?? .zero)
|
2022-11-09 09:26:12 -07:00
|
|
|
|
2024-06-03 04:13:52 -06:00
|
|
|
if currentTimeSecs > duration || didEnd {
|
|
|
|
currentTimeSecs = duration
|
|
|
|
}
|
2022-11-09 09:26:12 -07:00
|
|
|
|
2022-05-19 07:29:25 -06:00
|
|
|
if currentTimeSecs >= 0 {
|
2023-12-07 00:47:40 -07:00
|
|
|
#if USE_GOOGLE_IMA
|
|
|
|
if !_didRequestAds && currentTimeSecs >= 0.0001 && _adTagUrl != nil {
|
|
|
|
_imaAdsManager.requestAds()
|
|
|
|
_didRequestAds = true
|
|
|
|
}
|
|
|
|
#endif
|
2022-05-19 07:29:25 -06:00
|
|
|
onVideoProgress?([
|
2024-06-03 04:13:52 -06:00
|
|
|
"currentTime": currentTimeSecs,
|
2023-02-07 13:50:54 -07:00
|
|
|
"playableDuration": RCTVideoUtils.calculatePlayableDuration(_player, withSource: _source),
|
2024-06-03 04:13:52 -06:00
|
|
|
"atValue": currentTime?.value ?? .zero,
|
|
|
|
"currentPlaybackTime": NSNumber(value: Double(currentPlaybackTime?.timeIntervalSince1970 ?? 0 * 1000)).int64Value,
|
2022-05-19 07:29:25 -06:00
|
|
|
"target": reactTag,
|
2023-12-07 00:47:40 -07:00
|
|
|
"seekableDuration": RCTVideoUtils.calculateSeekableDuration(_player),
|
2022-05-19 07:29:25 -06:00
|
|
|
])
|
|
|
|
}
|
|
|
|
}
|
2022-11-09 09:26:12 -07:00
|
|
|
|
2024-03-14 04:46:45 -06:00
|
|
|
var isSetSourceOngoing = false
|
|
|
|
var nextSource: NSDictionary?
|
|
|
|
|
|
|
|
func applyNextSource() {
|
|
|
|
if self.nextSource != nil {
|
|
|
|
DebugLog("apply next source")
|
|
|
|
self.isSetSourceOngoing = false
|
|
|
|
let nextSrc = self.nextSource
|
|
|
|
self.nextSource = nil
|
|
|
|
self.setSrc(nextSrc)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-05-19 07:29:25 -06:00
|
|
|
// MARK: - Player and source
|
2023-12-07 00:47:40 -07:00
|
|
|
|
2024-04-04 05:23:44 -06:00
|
|
|
func preparePlayerItem() async throws -> AVPlayerItem {
|
2024-05-07 04:30:57 -06:00
|
|
|
guard let source = _source else {
|
2024-04-04 05:23:44 -06:00
|
|
|
DebugLog("The source not exist")
|
2024-05-07 04:30:57 -06:00
|
|
|
isSetSourceOngoing = false
|
|
|
|
applyNextSource()
|
2024-04-04 05:23:44 -06:00
|
|
|
throw NSError(domain: "", code: 0, userInfo: nil)
|
|
|
|
}
|
|
|
|
|
2024-05-13 13:32:51 -06:00
|
|
|
// Perform on next run loop, otherwise onVideoLoadStart is nil
|
|
|
|
onVideoLoadStart?([
|
|
|
|
"src": [
|
|
|
|
"uri": _source?.uri ?? NSNull(),
|
|
|
|
"type": _source?.type ?? NSNull(),
|
|
|
|
"isNetwork": NSNumber(value: _source?.isNetwork ?? false),
|
|
|
|
],
|
2024-07-10 04:17:22 -06:00
|
|
|
"drm": source.drm?.json ?? NSNull(),
|
2024-05-13 13:32:51 -06:00
|
|
|
"target": reactTag,
|
|
|
|
])
|
|
|
|
|
2024-04-04 05:23:44 -06:00
|
|
|
if let uri = source.uri, uri.starts(with: "ph://") {
|
|
|
|
let photoAsset = await RCTVideoUtils.preparePHAsset(uri: uri)
|
2024-05-07 04:30:57 -06:00
|
|
|
return await playerItemPrepareText(asset: photoAsset, assetOptions: nil, uri: source.uri ?? "")
|
2024-04-04 05:23:44 -06:00
|
|
|
}
|
|
|
|
|
|
|
|
guard let assetResult = RCTVideoUtils.prepareAsset(source: source),
|
|
|
|
let asset = assetResult.asset,
|
|
|
|
let assetOptions = assetResult.assetOptions else {
|
2024-05-07 04:30:57 -06:00
|
|
|
DebugLog("Could not find video URL in source '\(String(describing: _source))'")
|
|
|
|
isSetSourceOngoing = false
|
|
|
|
applyNextSource()
|
2024-04-04 05:23:44 -06:00
|
|
|
throw NSError(domain: "", code: 0, userInfo: nil)
|
|
|
|
}
|
|
|
|
|
|
|
|
guard let assetResult = RCTVideoUtils.prepareAsset(source: source),
|
|
|
|
let asset = assetResult.asset,
|
|
|
|
let assetOptions = assetResult.assetOptions else {
|
2024-05-07 04:30:57 -06:00
|
|
|
DebugLog("Could not find video URL in source '\(String(describing: _source))'")
|
|
|
|
isSetSourceOngoing = false
|
|
|
|
applyNextSource()
|
2024-04-04 05:23:44 -06:00
|
|
|
throw NSError(domain: "", code: 0, userInfo: nil)
|
|
|
|
}
|
|
|
|
|
2024-05-07 04:30:57 -06:00
|
|
|
if let startPosition = _source?.startPosition {
|
|
|
|
_startPosition = startPosition / 1000
|
2024-04-04 05:23:44 -06:00
|
|
|
}
|
|
|
|
|
|
|
|
#if USE_VIDEO_CACHING
|
2024-05-07 04:30:57 -06:00
|
|
|
if _videoCache.shouldCache(source: source, textTracks: _textTracks) {
|
|
|
|
return try await _videoCache.playerItemForSourceUsingCache(uri: source.uri, assetOptions: assetOptions)
|
2024-04-04 05:23:44 -06:00
|
|
|
}
|
|
|
|
#endif
|
|
|
|
|
2024-07-10 04:17:22 -06:00
|
|
|
if source.drm != nil || _localSourceEncryptionKeyScheme != nil {
|
2024-05-07 04:30:57 -06:00
|
|
|
_resouceLoaderDelegate = RCTResourceLoaderDelegate(
|
2024-04-04 05:23:44 -06:00
|
|
|
asset: asset,
|
2024-07-10 04:17:22 -06:00
|
|
|
drm: source.drm,
|
2024-05-07 04:30:57 -06:00
|
|
|
localSourceEncryptionKeyScheme: _localSourceEncryptionKeyScheme,
|
|
|
|
onVideoError: onVideoError,
|
|
|
|
onGetLicense: onGetLicense,
|
|
|
|
reactTag: reactTag
|
2024-04-04 05:23:44 -06:00
|
|
|
)
|
|
|
|
}
|
|
|
|
|
|
|
|
return await playerItemPrepareText(asset: asset, assetOptions: assetOptions, uri: source.uri ?? "")
|
|
|
|
}
|
|
|
|
|
|
|
|
func setupPlayer(playerItem: AVPlayerItem) async throws {
|
2024-05-07 04:30:57 -06:00
|
|
|
if !isSetSourceOngoing {
|
2024-04-04 05:23:44 -06:00
|
|
|
DebugLog("setSrc has been canceled last step")
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
2024-05-07 04:30:57 -06:00
|
|
|
_player?.pause()
|
|
|
|
_playerItem = playerItem
|
|
|
|
_playerObserver.playerItem = _playerItem
|
|
|
|
setPreferredForwardBufferDuration(_preferredForwardBufferDuration)
|
|
|
|
setPlaybackRange(playerItem, withCropStart: _source?.cropStart, withCropEnd: _source?.cropEnd)
|
|
|
|
setFilter(_filterName)
|
|
|
|
if let maxBitRate = _maxBitRate {
|
|
|
|
_playerItem?.preferredPeakBitRate = Double(maxBitRate)
|
2024-04-04 05:23:44 -06:00
|
|
|
}
|
|
|
|
|
2024-05-07 04:30:57 -06:00
|
|
|
if _player == nil {
|
|
|
|
_player = AVPlayer()
|
2024-06-25 00:55:32 -06:00
|
|
|
ReactNativeVideoManager.shared.onInstanceCreated(id: instanceId, player: _player)
|
|
|
|
|
2024-05-07 04:30:57 -06:00
|
|
|
_player!.replaceCurrentItem(with: playerItem)
|
2024-04-04 05:23:44 -06:00
|
|
|
|
2024-05-31 00:57:17 -06:00
|
|
|
if _showNotificationControls {
|
|
|
|
// We need to register player after we set current item and only for init
|
|
|
|
NowPlayingInfoCenterManager.shared.registerPlayer(player: _player!)
|
|
|
|
}
|
2024-05-07 04:30:57 -06:00
|
|
|
} else {
|
2024-09-02 07:42:51 -06:00
|
|
|
#if !os(tvOS)
|
|
|
|
if #available(iOS 16.0, *) {
|
|
|
|
// This feature caused crashes, if the app was put in bg, before the source change
|
|
|
|
// https://github.com/TheWidlarzGroup/react-native-video/issues/3900
|
|
|
|
self._playerViewController?.allowsVideoFrameAnalysis = false
|
|
|
|
}
|
|
|
|
#endif
|
2024-05-07 04:30:57 -06:00
|
|
|
_player?.replaceCurrentItem(with: playerItem)
|
2024-09-02 07:42:51 -06:00
|
|
|
#if !os(tvOS)
|
|
|
|
if #available(iOS 16.0, *) {
|
|
|
|
self._playerViewController?.allowsVideoFrameAnalysis = true
|
|
|
|
}
|
|
|
|
#endif
|
2024-08-02 02:49:52 -06:00
|
|
|
// later we can just call "updateNowPlayingInfo:
|
|
|
|
NowPlayingInfoCenterManager.shared.updateNowPlayingInfo()
|
2024-05-07 04:30:57 -06:00
|
|
|
}
|
|
|
|
|
|
|
|
_playerObserver.player = _player
|
|
|
|
applyModifiers()
|
|
|
|
_player?.actionAtItemEnd = .none
|
2024-04-04 05:23:44 -06:00
|
|
|
|
|
|
|
if #available(iOS 10.0, *) {
|
2024-05-07 04:30:57 -06:00
|
|
|
setAutomaticallyWaitsToMinimizeStalling(_automaticallyWaitsToMinimizeStalling)
|
2024-04-04 05:23:44 -06:00
|
|
|
}
|
|
|
|
|
|
|
|
#if USE_GOOGLE_IMA
|
2024-05-07 04:30:57 -06:00
|
|
|
if _adTagUrl != nil {
|
2024-04-04 05:23:44 -06:00
|
|
|
// Set up your content playhead and contentComplete callback.
|
2024-05-07 04:30:57 -06:00
|
|
|
_contentPlayhead = IMAAVPlayerContentPlayhead(avPlayer: _player!)
|
2024-04-04 05:23:44 -06:00
|
|
|
|
2024-05-07 04:30:57 -06:00
|
|
|
_imaAdsManager.setUpAdsLoader()
|
2024-04-04 05:23:44 -06:00
|
|
|
}
|
|
|
|
#endif
|
2024-05-07 04:30:57 -06:00
|
|
|
isSetSourceOngoing = false
|
|
|
|
applyNextSource()
|
2024-04-04 05:23:44 -06:00
|
|
|
}
|
|
|
|
|
2022-05-19 07:29:25 -06:00
|
|
|
@objc
|
2023-12-07 00:47:40 -07:00
|
|
|
func setSrc(_ source: NSDictionary!) {
|
2024-03-14 04:46:45 -06:00
|
|
|
if self.isSetSourceOngoing || self.nextSource != nil {
|
|
|
|
DebugLog("setSrc buffer request")
|
|
|
|
self._player?.replaceCurrentItem(with: nil)
|
|
|
|
nextSource = source
|
|
|
|
return
|
|
|
|
}
|
|
|
|
self.isSetSourceOngoing = true
|
|
|
|
|
2024-04-04 05:23:44 -06:00
|
|
|
let initializeSource = {
|
2022-12-21 14:11:09 -07:00
|
|
|
self._source = VideoSource(source)
|
2023-12-07 00:47:40 -07:00
|
|
|
if self._source?.uri == nil || self._source?.uri == "" {
|
2022-10-02 13:33:53 -06:00
|
|
|
self._player?.replaceCurrentItem(with: nil)
|
2024-03-14 04:46:45 -06:00
|
|
|
self.isSetSourceOngoing = false
|
|
|
|
self.applyNextSource()
|
2024-05-07 04:30:57 -06:00
|
|
|
|
|
|
|
if let player = self._player {
|
|
|
|
NowPlayingInfoCenterManager.shared.removePlayer(player: player)
|
|
|
|
}
|
|
|
|
|
2024-03-14 04:46:45 -06:00
|
|
|
DebugLog("setSrc Stopping playback")
|
2023-12-07 00:47:40 -07:00
|
|
|
return
|
2022-10-02 13:33:53 -06:00
|
|
|
}
|
2022-12-21 14:11:09 -07:00
|
|
|
self.removePlayerLayer()
|
|
|
|
self._playerObserver.player = nil
|
2023-01-23 08:49:46 -07:00
|
|
|
self._resouceLoaderDelegate = nil
|
2022-12-21 14:11:09 -07:00
|
|
|
self._playerObserver.playerItem = nil
|
2022-12-17 14:33:49 -07:00
|
|
|
|
|
|
|
// perform on next run loop, otherwise other passed react-props may not be set
|
2024-04-04 05:23:44 -06:00
|
|
|
RCTVideoUtils.delay { [weak self] in
|
|
|
|
do {
|
2024-01-04 12:16:23 -07:00
|
|
|
guard let self else { throw NSError(domain: "", code: 0, userInfo: nil) }
|
2022-12-17 14:33:49 -07:00
|
|
|
|
2024-04-04 05:23:44 -06:00
|
|
|
let playerItem = try await self.preparePlayerItem()
|
2024-06-07 06:04:00 -06:00
|
|
|
try await self.setupPlayer(playerItem: playerItem)
|
2024-04-04 05:23:44 -06:00
|
|
|
} catch {
|
|
|
|
DebugLog("An error occurred: \(error.localizedDescription)")
|
2022-11-09 09:26:12 -07:00
|
|
|
|
2024-04-04 05:23:44 -06:00
|
|
|
if let self {
|
|
|
|
self.onVideoError?(["error": error.localizedDescription])
|
|
|
|
self.isSetSourceOngoing = false
|
|
|
|
self.applyNextSource()
|
2024-05-07 04:30:57 -06:00
|
|
|
|
|
|
|
if let player = self._player {
|
|
|
|
NowPlayingInfoCenterManager.shared.removePlayer(player: player)
|
|
|
|
}
|
2022-12-17 14:33:49 -07:00
|
|
|
}
|
2024-03-14 04:46:45 -06:00
|
|
|
}
|
2024-04-04 05:23:44 -06:00
|
|
|
}
|
|
|
|
|
2022-12-21 23:17:33 -07:00
|
|
|
self._videoLoadStarted = true
|
2024-03-14 04:46:45 -06:00
|
|
|
self.applyNextSource()
|
2022-12-17 14:33:49 -07:00
|
|
|
}
|
2024-04-04 05:23:44 -06:00
|
|
|
|
|
|
|
DispatchQueue.global(qos: .default).async(execute: initializeSource)
|
2022-05-19 07:29:25 -06:00
|
|
|
}
|
2022-11-09 09:26:12 -07:00
|
|
|
|
2022-05-19 07:29:25 -06:00
|
|
|
@objc
|
2023-12-07 00:47:40 -07:00
|
|
|
func setLocalSourceEncryptionKeyScheme(_ keyScheme: String) {
|
2022-05-19 07:29:25 -06:00
|
|
|
_localSourceEncryptionKeyScheme = keyScheme
|
|
|
|
}
|
2022-11-09 09:26:12 -07:00
|
|
|
|
2024-04-04 05:23:44 -06:00
|
|
|
func playerItemPrepareText(asset: AVAsset!, assetOptions: NSDictionary?, uri: String) async -> AVPlayerItem {
|
2024-09-02 07:40:38 -06:00
|
|
|
if self._textTracks.isEmpty == true || (uri.hasSuffix(".m3u8")) {
|
2024-05-07 04:30:57 -06:00
|
|
|
return await self.playerItemPropegateMetadata(AVPlayerItem(asset: asset))
|
2024-04-04 05:23:44 -06:00
|
|
|
}
|
2024-01-06 12:06:53 -07:00
|
|
|
|
2024-04-04 05:23:44 -06:00
|
|
|
// AVPlayer can't airplay AVMutableCompositions
|
|
|
|
self._allowsExternalPlayback = false
|
|
|
|
let mixComposition = await RCTVideoUtils.generateMixComposition(asset)
|
|
|
|
let validTextTracks = await RCTVideoUtils.getValidTextTracks(
|
|
|
|
asset: asset,
|
|
|
|
assetOptions: assetOptions,
|
|
|
|
mixComposition: mixComposition,
|
|
|
|
textTracks: self._textTracks
|
|
|
|
)
|
2022-11-09 09:26:12 -07:00
|
|
|
|
2024-09-02 11:08:27 -06:00
|
|
|
if validTextTracks.isEmpty {
|
|
|
|
DebugLog("Strange state, not valid textTrack")
|
|
|
|
}
|
|
|
|
|
2024-09-02 07:40:38 -06:00
|
|
|
if validTextTracks.count != self._textTracks.count {
|
2024-04-04 05:23:44 -06:00
|
|
|
self.setTextTracks(validTextTracks)
|
2024-01-06 12:06:53 -07:00
|
|
|
}
|
2024-04-04 05:23:44 -06:00
|
|
|
|
2024-05-07 04:30:57 -06:00
|
|
|
return await self.playerItemPropegateMetadata(AVPlayerItem(asset: mixComposition))
|
2023-08-12 16:01:27 -06:00
|
|
|
}
|
2023-11-17 00:19:39 -07:00
|
|
|
|
2024-05-07 04:30:57 -06:00
|
|
|
func playerItemPropegateMetadata(_ playerItem: AVPlayerItem!) async -> AVPlayerItem {
|
2023-08-12 16:01:27 -06:00
|
|
|
var mapping: [AVMetadataIdentifier: Any] = [:]
|
2023-11-17 00:19:39 -07:00
|
|
|
|
2024-05-07 04:30:57 -06:00
|
|
|
if let title = _source?.customMetadata?.title {
|
2023-08-12 16:01:27 -06:00
|
|
|
mapping[.commonIdentifierTitle] = title
|
|
|
|
}
|
2023-11-17 00:19:39 -07:00
|
|
|
|
2024-05-07 04:30:57 -06:00
|
|
|
if let artist = _source?.customMetadata?.artist {
|
|
|
|
mapping[.commonIdentifierArtist] = artist
|
|
|
|
}
|
|
|
|
|
|
|
|
if let subtitle = _source?.customMetadata?.subtitle {
|
2023-08-12 16:01:27 -06:00
|
|
|
mapping[.iTunesMetadataTrackSubTitle] = subtitle
|
|
|
|
}
|
2023-11-17 00:19:39 -07:00
|
|
|
|
2024-05-07 04:30:57 -06:00
|
|
|
if let description = _source?.customMetadata?.description {
|
2023-08-12 16:01:27 -06:00
|
|
|
mapping[.commonIdentifierDescription] = description
|
|
|
|
}
|
2023-11-17 00:19:39 -07:00
|
|
|
|
2024-05-07 04:30:57 -06:00
|
|
|
if let imageUri = _source?.customMetadata?.imageUri,
|
|
|
|
let imageData = await RCTVideoUtils.createImageMetadataItem(imageUri: imageUri) {
|
2023-10-07 07:14:10 -06:00
|
|
|
mapping[.commonIdentifierArtwork] = imageData
|
|
|
|
}
|
2023-11-17 00:19:39 -07:00
|
|
|
|
2023-08-15 10:21:45 -06:00
|
|
|
if #available(iOS 12.2, *), !mapping.isEmpty {
|
2023-09-09 08:15:51 -06:00
|
|
|
playerItem.externalMetadata = RCTVideoUtils.createMetadataItems(for: mapping)
|
2023-08-12 16:01:27 -06:00
|
|
|
}
|
2023-11-17 00:19:39 -07:00
|
|
|
|
2023-12-07 00:47:40 -07:00
|
|
|
#if os(tvOS)
|
|
|
|
if let chapters = _chapters {
|
|
|
|
playerItem.navigationMarkerGroups = RCTVideoTVUtils.makeNavigationMarkerGroups(chapters)
|
|
|
|
}
|
|
|
|
#endif
|
2023-11-17 00:19:39 -07:00
|
|
|
|
2023-08-12 16:01:27 -06:00
|
|
|
return playerItem
|
2022-05-19 07:29:25 -06:00
|
|
|
}
|
2022-11-09 09:26:12 -07:00
|
|
|
|
2022-05-19 07:29:25 -06:00
|
|
|
// MARK: - Prop setters
|
2022-11-09 09:26:12 -07:00
|
|
|
|
2022-05-19 07:29:25 -06:00
|
|
|
@objc
|
2023-10-11 13:56:54 -06:00
|
|
|
func setResizeMode(_ mode: String) {
|
|
|
|
var resizeMode: AVLayerVideoGravity = .resizeAspect
|
2023-11-17 00:19:39 -07:00
|
|
|
|
2023-10-11 13:56:54 -06:00
|
|
|
switch mode {
|
|
|
|
case "contain":
|
|
|
|
resizeMode = .resizeAspect
|
|
|
|
case "none":
|
|
|
|
resizeMode = .resizeAspect
|
|
|
|
case "cover":
|
|
|
|
resizeMode = .resizeAspectFill
|
|
|
|
case "stretch":
|
|
|
|
resizeMode = .resize
|
|
|
|
default:
|
|
|
|
resizeMode = .resizeAspect
|
|
|
|
}
|
2023-11-17 00:19:39 -07:00
|
|
|
|
2022-05-19 07:29:25 -06:00
|
|
|
if _controls {
|
2023-10-11 13:56:54 -06:00
|
|
|
_playerViewController?.videoGravity = resizeMode
|
2022-05-19 07:29:25 -06:00
|
|
|
} else {
|
2023-10-11 13:56:54 -06:00
|
|
|
_playerLayer?.videoGravity = resizeMode
|
2022-05-19 07:29:25 -06:00
|
|
|
}
|
2023-11-17 00:19:39 -07:00
|
|
|
|
2022-05-19 07:29:25 -06:00
|
|
|
_resizeMode = mode
|
|
|
|
}
|
2022-11-09 09:26:12 -07:00
|
|
|
|
2022-05-19 07:29:25 -06:00
|
|
|
@objc
|
2023-12-07 00:47:40 -07:00
|
|
|
func setPlayInBackground(_ playInBackground: Bool) {
|
2022-05-19 07:29:25 -06:00
|
|
|
_playInBackground = playInBackground
|
|
|
|
}
|
2022-11-09 09:26:12 -07:00
|
|
|
|
2022-05-19 07:29:25 -06:00
|
|
|
@objc
|
2023-12-07 00:47:40 -07:00
|
|
|
func setPreventsDisplaySleepDuringVideoPlayback(_ preventsDisplaySleepDuringVideoPlayback: Bool) {
|
2022-05-19 07:29:25 -06:00
|
|
|
_preventsDisplaySleepDuringVideoPlayback = preventsDisplaySleepDuringVideoPlayback
|
|
|
|
self.applyModifiers()
|
|
|
|
}
|
2022-11-09 09:26:12 -07:00
|
|
|
|
2022-05-19 07:29:25 -06:00
|
|
|
@objc
|
2023-12-07 00:47:40 -07:00
|
|
|
func setAllowsExternalPlayback(_ allowsExternalPlayback: Bool) {
|
2022-05-19 07:29:25 -06:00
|
|
|
_allowsExternalPlayback = allowsExternalPlayback
|
2024-01-15 00:04:29 -07:00
|
|
|
#if !os(visionOS)
|
|
|
|
_player?.allowsExternalPlayback = _allowsExternalPlayback
|
|
|
|
#endif
|
2022-05-19 07:29:25 -06:00
|
|
|
}
|
2022-11-09 09:26:12 -07:00
|
|
|
|
2022-05-19 07:29:25 -06:00
|
|
|
@objc
|
2023-12-07 00:47:40 -07:00
|
|
|
func setPlayWhenInactive(_ playWhenInactive: Bool) {
|
2022-05-19 07:29:25 -06:00
|
|
|
_playWhenInactive = playWhenInactive
|
|
|
|
}
|
2022-11-09 09:26:12 -07:00
|
|
|
|
2022-05-19 07:29:25 -06:00
|
|
|
@objc
|
2023-12-07 00:47:40 -07:00
|
|
|
func setPictureInPicture(_ pictureInPicture: Bool) {
|
|
|
|
#if os(iOS)
|
|
|
|
let audioSession = AVAudioSession.sharedInstance()
|
|
|
|
do {
|
|
|
|
try audioSession.setCategory(.playback)
|
|
|
|
try audioSession.setActive(true, options: [])
|
|
|
|
} catch {}
|
|
|
|
if pictureInPicture {
|
|
|
|
_pictureInPictureEnabled = true
|
|
|
|
} else {
|
|
|
|
_pictureInPictureEnabled = false
|
|
|
|
}
|
|
|
|
_pip?.setPictureInPicture(pictureInPicture)
|
|
|
|
#endif
|
2022-05-19 07:29:25 -06:00
|
|
|
}
|
2022-11-09 09:26:12 -07:00
|
|
|
|
2022-05-19 07:29:25 -06:00
|
|
|
@objc
|
2023-12-07 00:47:40 -07:00
|
|
|
func setRestoreUserInterfaceForPIPStopCompletionHandler(_ restore: Bool) {
|
|
|
|
#if os(iOS)
|
2024-04-04 07:08:48 -06:00
|
|
|
if _pip != nil {
|
|
|
|
_pip?.setRestoreUserInterfaceForPIPStopCompletionHandler(restore)
|
|
|
|
} else {
|
|
|
|
_playerObserver.setRestoreUserInterfaceForPIPStopCompletionHandler(restore)
|
|
|
|
}
|
2023-12-07 00:47:40 -07:00
|
|
|
#endif
|
2022-05-19 07:29:25 -06:00
|
|
|
}
|
2022-11-09 09:26:12 -07:00
|
|
|
|
2022-05-19 07:29:25 -06:00
|
|
|
@objc
|
2023-12-07 00:47:40 -07:00
|
|
|
func setIgnoreSilentSwitch(_ ignoreSilentSwitch: String?) {
|
2022-05-19 07:29:25 -06:00
|
|
|
_ignoreSilentSwitch = ignoreSilentSwitch
|
2023-12-07 00:47:40 -07:00
|
|
|
RCTPlayerOperations.configureAudio(ignoreSilentSwitch: _ignoreSilentSwitch, mixWithOthers: _mixWithOthers, audioOutput: _audioOutput)
|
2022-05-19 07:29:25 -06:00
|
|
|
applyModifiers()
|
|
|
|
}
|
2022-11-09 09:26:12 -07:00
|
|
|
|
2022-05-19 07:29:25 -06:00
|
|
|
@objc
|
2023-12-07 00:47:40 -07:00
|
|
|
func setMixWithOthers(_ mixWithOthers: String?) {
|
2022-05-19 07:29:25 -06:00
|
|
|
_mixWithOthers = mixWithOthers
|
|
|
|
applyModifiers()
|
|
|
|
}
|
2022-11-09 09:26:12 -07:00
|
|
|
|
2022-05-19 07:29:25 -06:00
|
|
|
@objc
|
2023-12-07 00:47:40 -07:00
|
|
|
func setPaused(_ paused: Bool) {
|
2022-05-19 07:29:25 -06:00
|
|
|
if paused {
|
2022-11-14 05:42:39 -07:00
|
|
|
if _adPlaying {
|
2023-12-07 00:47:40 -07:00
|
|
|
#if USE_GOOGLE_IMA
|
|
|
|
_imaAdsManager.getAdsManager()?.pause()
|
|
|
|
#endif
|
2022-11-14 05:42:39 -07:00
|
|
|
} else {
|
|
|
|
_player?.pause()
|
|
|
|
_player?.rate = 0.0
|
|
|
|
}
|
2022-05-19 07:29:25 -06:00
|
|
|
} else {
|
2023-12-07 00:47:40 -07:00
|
|
|
RCTPlayerOperations.configureAudio(ignoreSilentSwitch: _ignoreSilentSwitch, mixWithOthers: _mixWithOthers, audioOutput: _audioOutput)
|
2022-11-09 09:26:12 -07:00
|
|
|
|
2022-11-14 05:42:39 -07:00
|
|
|
if _adPlaying {
|
2023-12-07 00:47:40 -07:00
|
|
|
#if USE_GOOGLE_IMA
|
|
|
|
_imaAdsManager.getAdsManager()?.resume()
|
|
|
|
#endif
|
2022-05-19 07:29:25 -06:00
|
|
|
} else {
|
2022-11-14 05:42:39 -07:00
|
|
|
if #available(iOS 10.0, *), !_automaticallyWaitsToMinimizeStalling {
|
|
|
|
_player?.playImmediately(atRate: _rate)
|
|
|
|
} else {
|
|
|
|
_player?.play()
|
|
|
|
_player?.rate = _rate
|
|
|
|
}
|
2022-05-19 07:29:25 -06:00
|
|
|
_player?.rate = _rate
|
|
|
|
}
|
|
|
|
}
|
2022-11-09 09:26:12 -07:00
|
|
|
|
2022-05-19 07:29:25 -06:00
|
|
|
_paused = paused
|
|
|
|
}
|
2022-11-09 09:26:12 -07:00
|
|
|
|
2022-05-19 07:29:25 -06:00
|
|
|
@objc
|
2024-07-12 02:27:42 -06:00
|
|
|
func setSeek(_ time: NSNumber, _ tolerance: NSNumber) {
|
2023-12-07 00:47:40 -07:00
|
|
|
let item: AVPlayerItem? = _player?.currentItem
|
2024-07-11 02:08:36 -06:00
|
|
|
|
|
|
|
_pendingSeek = true
|
|
|
|
|
2024-01-04 12:16:23 -07:00
|
|
|
guard item != nil, let player = _player, let item, item.status == AVPlayerItem.Status.readyToPlay else {
|
2024-07-12 02:27:42 -06:00
|
|
|
_pendingSeekTime = time.floatValue
|
2022-05-19 07:29:25 -06:00
|
|
|
return
|
|
|
|
}
|
2022-11-09 09:26:12 -07:00
|
|
|
|
2022-05-19 07:29:25 -06:00
|
|
|
RCTPlayerOperations.seek(
|
2023-12-07 00:47:40 -07:00
|
|
|
player: player,
|
|
|
|
playerItem: item,
|
2024-04-05 02:35:57 -06:00
|
|
|
paused: _paused,
|
2024-07-12 02:27:42 -06:00
|
|
|
seekTime: time.floatValue,
|
|
|
|
seekTolerance: tolerance.floatValue
|
2024-04-04 05:23:44 -06:00
|
|
|
) { [weak self] (_: Bool) in
|
2024-01-04 12:16:23 -07:00
|
|
|
guard let self else { return }
|
2022-11-09 09:26:12 -07:00
|
|
|
|
2023-11-17 00:19:39 -07:00
|
|
|
self._playerObserver.addTimeObserverIfNotSet()
|
2024-06-07 06:04:00 -06:00
|
|
|
self.setPaused(self._paused)
|
2023-11-17 00:19:39 -07:00
|
|
|
self.onVideoSeek?(["currentTime": NSNumber(value: Float(CMTimeGetSeconds(item.currentTime()))),
|
2024-07-12 02:27:42 -06:00
|
|
|
"seekTime": time,
|
2023-11-17 00:19:39 -07:00
|
|
|
"target": self.reactTag])
|
2024-04-04 05:23:44 -06:00
|
|
|
}
|
2022-11-09 09:26:12 -07:00
|
|
|
|
2022-05-19 07:29:25 -06:00
|
|
|
_pendingSeek = false
|
|
|
|
}
|
2022-11-09 09:26:12 -07:00
|
|
|
|
2022-05-19 07:29:25 -06:00
|
|
|
@objc
|
2023-12-07 00:47:40 -07:00
|
|
|
func setRate(_ rate: Float) {
|
2024-04-07 11:04:43 -06:00
|
|
|
if _rate != 1 {
|
|
|
|
// This is a workaround
|
|
|
|
// when player change from rate != 1 to another rate != 1 we see some video blocking
|
|
|
|
// To bypass it we shall force the rate to 1 and apply real valut afterward
|
|
|
|
_player?.rate = 1
|
|
|
|
DispatchQueue.main.asyncAfter(deadline: .now() + 0.1) {
|
|
|
|
self._rate = rate
|
|
|
|
self.applyModifiers()
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
// apply it directly
|
|
|
|
self._rate = rate
|
|
|
|
self.applyModifiers()
|
|
|
|
}
|
2022-05-19 07:29:25 -06:00
|
|
|
}
|
2022-11-09 09:26:12 -07:00
|
|
|
|
2023-03-28 05:14:48 -06:00
|
|
|
@objc
|
|
|
|
func isMuted() -> Bool {
|
|
|
|
return _muted
|
|
|
|
}
|
|
|
|
|
2022-05-19 07:29:25 -06:00
|
|
|
@objc
|
2023-12-07 00:47:40 -07:00
|
|
|
func setMuted(_ muted: Bool) {
|
2022-05-19 07:29:25 -06:00
|
|
|
_muted = muted
|
|
|
|
applyModifiers()
|
|
|
|
}
|
2022-11-09 09:26:12 -07:00
|
|
|
|
2023-04-02 12:02:56 -06:00
|
|
|
@objc
|
2023-12-07 00:47:40 -07:00
|
|
|
func setAudioOutput(_ audioOutput: String) {
|
2023-04-02 12:02:56 -06:00
|
|
|
_audioOutput = audioOutput
|
2023-12-07 00:47:40 -07:00
|
|
|
RCTPlayerOperations.configureAudio(ignoreSilentSwitch: _ignoreSilentSwitch, mixWithOthers: _mixWithOthers, audioOutput: _audioOutput)
|
2023-04-02 12:02:56 -06:00
|
|
|
do {
|
|
|
|
if audioOutput == "speaker" {
|
2024-01-15 00:04:29 -07:00
|
|
|
#if os(iOS) || os(visionOS)
|
2023-12-07 00:47:40 -07:00
|
|
|
try AVAudioSession.sharedInstance().overrideOutputAudioPort(AVAudioSession.PortOverride.speaker)
|
|
|
|
#endif
|
2023-04-02 12:02:56 -06:00
|
|
|
} else if audioOutput == "earpiece" {
|
|
|
|
try AVAudioSession.sharedInstance().overrideOutputAudioPort(AVAudioSession.PortOverride.none)
|
|
|
|
}
|
|
|
|
} catch {
|
|
|
|
print("Error occurred: \(error.localizedDescription)")
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-05-19 07:29:25 -06:00
|
|
|
@objc
|
2023-12-07 00:47:40 -07:00
|
|
|
func setVolume(_ volume: Float) {
|
2022-05-19 07:29:25 -06:00
|
|
|
_volume = volume
|
|
|
|
applyModifiers()
|
|
|
|
}
|
2022-11-09 09:26:12 -07:00
|
|
|
|
2022-05-19 07:29:25 -06:00
|
|
|
@objc
|
2023-12-07 00:47:40 -07:00
|
|
|
func setMaxBitRate(_ maxBitRate: Float) {
|
2022-05-19 07:29:25 -06:00
|
|
|
_maxBitRate = maxBitRate
|
|
|
|
_playerItem?.preferredPeakBitRate = Double(maxBitRate)
|
|
|
|
}
|
2022-11-09 09:26:12 -07:00
|
|
|
|
2022-05-19 07:29:25 -06:00
|
|
|
@objc
|
2023-12-07 00:47:40 -07:00
|
|
|
func setPreferredForwardBufferDuration(_ preferredForwardBufferDuration: Float) {
|
2022-05-19 07:29:25 -06:00
|
|
|
_preferredForwardBufferDuration = preferredForwardBufferDuration
|
|
|
|
if #available(iOS 10.0, *) {
|
|
|
|
_playerItem?.preferredForwardBufferDuration = TimeInterval(preferredForwardBufferDuration)
|
|
|
|
} else {
|
|
|
|
// Fallback on earlier versions
|
|
|
|
}
|
|
|
|
}
|
2022-11-09 09:26:12 -07:00
|
|
|
|
2022-05-19 07:29:25 -06:00
|
|
|
@objc
|
2023-12-07 00:47:40 -07:00
|
|
|
func setAutomaticallyWaitsToMinimizeStalling(_ waits: Bool) {
|
2022-05-19 07:29:25 -06:00
|
|
|
_automaticallyWaitsToMinimizeStalling = waits
|
|
|
|
if #available(iOS 10.0, *) {
|
|
|
|
_player?.automaticallyWaitsToMinimizeStalling = waits
|
|
|
|
} else {
|
|
|
|
// Fallback on earlier versions
|
|
|
|
}
|
|
|
|
}
|
2023-08-12 04:18:47 -06:00
|
|
|
|
2024-03-22 06:43:46 -06:00
|
|
|
func setPlaybackRange(_ item: AVPlayerItem!, withCropStart cropStart: Int64?, withCropEnd cropEnd: Int64?) {
|
|
|
|
if let cropStart {
|
|
|
|
let start = CMTimeMake(value: cropStart, timescale: 1000)
|
2023-02-06 14:31:14 -07:00
|
|
|
item.reversePlaybackEndTime = start
|
|
|
|
_pendingSeekTime = Float(CMTimeGetSeconds(start))
|
|
|
|
_pendingSeek = true
|
2023-02-06 12:43:14 -07:00
|
|
|
}
|
2024-03-22 06:43:46 -06:00
|
|
|
if let cropEnd {
|
|
|
|
item.forwardPlaybackEndTime = CMTimeMake(value: cropEnd, timescale: 1000)
|
2023-02-06 12:43:14 -07:00
|
|
|
}
|
|
|
|
}
|
2022-11-09 09:26:12 -07:00
|
|
|
|
2022-05-19 07:29:25 -06:00
|
|
|
func applyModifiers() {
|
2023-07-12 01:11:48 -06:00
|
|
|
if let video = _player?.currentItem,
|
2023-11-17 00:19:39 -07:00
|
|
|
video == nil || video.status != AVPlayerItem.Status.readyToPlay {
|
2023-07-12 01:11:48 -06:00
|
|
|
return
|
|
|
|
}
|
2022-05-19 07:29:25 -06:00
|
|
|
if _muted {
|
|
|
|
if !_controls {
|
|
|
|
_player?.volume = 0
|
|
|
|
}
|
|
|
|
_player?.isMuted = true
|
|
|
|
} else {
|
|
|
|
_player?.volume = _volume
|
|
|
|
_player?.isMuted = false
|
|
|
|
}
|
2022-11-09 09:26:12 -07:00
|
|
|
|
2023-08-12 04:18:47 -06:00
|
|
|
if #available(iOS 12.0, tvOS 12.0, *) {
|
2024-01-15 00:04:29 -07:00
|
|
|
#if !os(visionOS)
|
|
|
|
_player?.preventsDisplaySleepDuringVideoPlayback = _preventsDisplaySleepDuringVideoPlayback
|
|
|
|
#endif
|
2022-05-19 07:29:25 -06:00
|
|
|
} else {
|
|
|
|
// Fallback on earlier versions
|
|
|
|
}
|
2022-11-09 09:26:12 -07:00
|
|
|
|
2024-01-04 12:16:23 -07:00
|
|
|
if let _maxBitRate {
|
2022-05-19 07:29:25 -06:00
|
|
|
setMaxBitRate(_maxBitRate)
|
|
|
|
}
|
2022-11-09 09:26:12 -07:00
|
|
|
|
2023-04-14 13:12:13 -06:00
|
|
|
setAudioOutput(_audioOutput)
|
2022-05-19 07:29:25 -06:00
|
|
|
setSelectedAudioTrack(_selectedAudioTrackCriteria)
|
|
|
|
setSelectedTextTrack(_selectedTextTrackCriteria)
|
|
|
|
setResizeMode(_resizeMode)
|
|
|
|
setRepeat(_repeat)
|
|
|
|
setControls(_controls)
|
|
|
|
setPaused(_paused)
|
|
|
|
setAllowsExternalPlayback(_allowsExternalPlayback)
|
|
|
|
}
|
2022-11-09 09:26:12 -07:00
|
|
|
|
2022-05-19 07:29:25 -06:00
|
|
|
@objc
|
2023-12-07 00:47:40 -07:00
|
|
|
func setRepeat(_ repeat: Bool) {
|
2022-05-19 07:29:25 -06:00
|
|
|
_repeat = `repeat`
|
|
|
|
}
|
2022-11-09 09:26:12 -07:00
|
|
|
|
2022-05-19 07:29:25 -06:00
|
|
|
@objc
|
2023-12-07 00:47:40 -07:00
|
|
|
func setSelectedAudioTrack(_ selectedAudioTrack: NSDictionary?) {
|
2022-05-19 07:29:25 -06:00
|
|
|
setSelectedAudioTrack(SelectedTrackCriteria(selectedAudioTrack))
|
|
|
|
}
|
2022-11-09 09:26:12 -07:00
|
|
|
|
2023-12-07 00:47:40 -07:00
|
|
|
func setSelectedAudioTrack(_ selectedAudioTrack: SelectedTrackCriteria?) {
|
2022-05-19 07:29:25 -06:00
|
|
|
_selectedAudioTrackCriteria = selectedAudioTrack
|
2024-04-04 05:23:44 -06:00
|
|
|
Task {
|
|
|
|
await RCTPlayerOperations.setMediaSelectionTrackForCharacteristic(player: _player, characteristic: AVMediaCharacteristic.audible,
|
|
|
|
criteria: _selectedAudioTrackCriteria)
|
|
|
|
}
|
2022-05-19 07:29:25 -06:00
|
|
|
}
|
2022-11-09 09:26:12 -07:00
|
|
|
|
2022-05-19 07:29:25 -06:00
|
|
|
@objc
|
2023-12-07 00:47:40 -07:00
|
|
|
func setSelectedTextTrack(_ selectedTextTrack: NSDictionary?) {
|
2022-05-19 07:29:25 -06:00
|
|
|
setSelectedTextTrack(SelectedTrackCriteria(selectedTextTrack))
|
|
|
|
}
|
2022-11-09 09:26:12 -07:00
|
|
|
|
2023-12-07 00:47:40 -07:00
|
|
|
func setSelectedTextTrack(_ selectedTextTrack: SelectedTrackCriteria?) {
|
2022-05-19 07:29:25 -06:00
|
|
|
_selectedTextTrackCriteria = selectedTextTrack
|
2024-09-02 07:40:38 -06:00
|
|
|
if !_textTracks.isEmpty { // sideloaded text tracks
|
|
|
|
RCTPlayerOperations.setSideloadedText(player: _player, textTracks: _textTracks, criteria: _selectedTextTrackCriteria)
|
2024-01-06 12:06:53 -07:00
|
|
|
} else { // text tracks included in the HLS playlist§
|
2024-04-04 05:23:44 -06:00
|
|
|
Task {
|
|
|
|
await RCTPlayerOperations.setMediaSelectionTrackForCharacteristic(player: _player, characteristic: AVMediaCharacteristic.legible,
|
|
|
|
criteria: _selectedTextTrackCriteria)
|
|
|
|
}
|
2022-05-19 07:29:25 -06:00
|
|
|
}
|
|
|
|
}
|
2022-11-09 09:26:12 -07:00
|
|
|
|
2022-05-19 07:29:25 -06:00
|
|
|
@objc
|
2023-12-07 00:47:40 -07:00
|
|
|
func setTextTracks(_ textTracks: [NSDictionary]?) {
|
2022-05-19 07:29:25 -06:00
|
|
|
setTextTracks(textTracks?.map { TextTrack($0) })
|
|
|
|
}
|
2022-11-09 09:26:12 -07:00
|
|
|
|
2023-12-07 00:47:40 -07:00
|
|
|
func setTextTracks(_ textTracks: [TextTrack]?) {
|
2024-09-02 07:40:38 -06:00
|
|
|
if textTracks == nil {
|
|
|
|
_textTracks = []
|
|
|
|
} else {
|
|
|
|
_textTracks = textTracks!
|
|
|
|
}
|
2022-05-19 07:29:25 -06:00
|
|
|
// in case textTracks was set after selectedTextTrack
|
2023-12-07 00:47:40 -07:00
|
|
|
if _selectedTextTrackCriteria != nil { setSelectedTextTrack(_selectedTextTrackCriteria) }
|
2022-05-19 07:29:25 -06:00
|
|
|
}
|
2023-11-17 00:19:39 -07:00
|
|
|
|
2023-08-12 16:01:27 -06:00
|
|
|
@objc
|
2023-12-07 00:47:40 -07:00
|
|
|
func setChapters(_ chapters: [NSDictionary]?) {
|
2023-08-12 16:01:27 -06:00
|
|
|
setChapters(chapters?.map { Chapter($0) })
|
|
|
|
}
|
|
|
|
|
2023-12-07 00:47:40 -07:00
|
|
|
func setChapters(_ chapters: [Chapter]?) {
|
2023-08-12 16:01:27 -06:00
|
|
|
_chapters = chapters
|
|
|
|
}
|
2022-11-09 09:26:12 -07:00
|
|
|
|
2022-05-19 07:29:25 -06:00
|
|
|
@objc
|
2023-12-07 00:47:40 -07:00
|
|
|
func setFullscreen(_ fullscreen: Bool) {
|
2024-07-04 04:46:42 -06:00
|
|
|
var alreadyFullscreenPresented = _presentingViewController?.presentedViewController != nil
|
|
|
|
if fullscreen && !_fullscreenPlayerPresented && _player != nil && !alreadyFullscreenPresented {
|
2022-05-19 07:29:25 -06:00
|
|
|
// Ensure player view controller is not null
|
2023-09-04 15:57:45 -06:00
|
|
|
// Controls will be displayed even if it is disabled in configuration
|
|
|
|
if _playerViewController == nil {
|
2022-05-19 07:29:25 -06:00
|
|
|
self.usePlayerViewController()
|
|
|
|
}
|
2022-11-09 09:26:12 -07:00
|
|
|
|
2022-05-19 07:29:25 -06:00
|
|
|
// Set presentation style to fullscreen
|
|
|
|
_playerViewController?.modalPresentationStyle = .fullScreen
|
2022-11-09 09:26:12 -07:00
|
|
|
|
2022-05-19 07:29:25 -06:00
|
|
|
// Find the nearest view controller
|
2023-12-07 00:47:40 -07:00
|
|
|
var viewController: UIViewController! = self.firstAvailableUIViewController()
|
|
|
|
if viewController == nil {
|
2023-12-28 04:58:25 -07:00
|
|
|
guard let keyWindow = RCTVideoUtils.getCurrentWindow() else { return }
|
|
|
|
|
2022-05-19 07:29:25 -06:00
|
|
|
viewController = keyWindow.rootViewController
|
2023-12-07 00:47:40 -07:00
|
|
|
if !viewController.children.isEmpty {
|
2022-05-19 07:29:25 -06:00
|
|
|
viewController = viewController.children.last
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if viewController != nil {
|
|
|
|
_presentingViewController = viewController
|
2022-11-09 09:26:12 -07:00
|
|
|
|
2022-05-19 07:29:25 -06:00
|
|
|
self.onVideoFullscreenPlayerWillPresent?(["target": reactTag as Any])
|
2022-11-09 09:26:12 -07:00
|
|
|
|
2022-12-21 23:07:06 -07:00
|
|
|
if let playerViewController = _playerViewController {
|
2023-12-07 00:47:40 -07:00
|
|
|
if _controls {
|
2024-05-07 09:25:09 -06:00
|
|
|
// prevents crash https://github.com/TheWidlarzGroup/react-native-video/issues/3040
|
2023-07-06 01:37:02 -06:00
|
|
|
self._playerViewController?.removeFromParent()
|
|
|
|
}
|
|
|
|
|
2023-12-07 00:47:40 -07:00
|
|
|
viewController.present(playerViewController, animated: true, completion: { [weak self] in
|
2024-01-04 12:16:23 -07:00
|
|
|
guard let self else { return }
|
2023-09-04 15:57:45 -06:00
|
|
|
// In fullscreen we must display controls
|
|
|
|
self._playerViewController?.showsPlaybackControls = true
|
2022-12-21 23:07:06 -07:00
|
|
|
self._fullscreenPlayerPresented = fullscreen
|
|
|
|
self._playerViewController?.autorotate = self._fullscreenAutorotate
|
2022-11-09 09:26:12 -07:00
|
|
|
|
2022-12-21 23:07:06 -07:00
|
|
|
self.onVideoFullscreenPlayerDidPresent?(["target": self.reactTag])
|
|
|
|
})
|
|
|
|
}
|
2022-05-19 07:29:25 -06:00
|
|
|
}
|
2024-01-04 12:16:23 -07:00
|
|
|
} else if !fullscreen && _fullscreenPlayerPresented, let _playerViewController {
|
2022-05-19 07:29:25 -06:00
|
|
|
self.videoPlayerViewControllerWillDismiss(playerViewController: _playerViewController)
|
2023-12-07 00:47:40 -07:00
|
|
|
_presentingViewController?.dismiss(animated: true, completion: { [weak self] in
|
2023-07-06 01:52:33 -06:00
|
|
|
self?.videoPlayerViewControllerDidDismiss(playerViewController: _playerViewController)
|
2022-05-19 07:29:25 -06:00
|
|
|
})
|
2024-06-10 14:41:26 -06:00
|
|
|
setControls(_controls)
|
2022-05-19 07:29:25 -06:00
|
|
|
}
|
|
|
|
}
|
2022-11-09 09:26:12 -07:00
|
|
|
|
2022-05-19 07:29:25 -06:00
|
|
|
@objc
|
2023-12-07 00:47:40 -07:00
|
|
|
func setFullscreenAutorotate(_ autorotate: Bool) {
|
2022-05-19 07:29:25 -06:00
|
|
|
_fullscreenAutorotate = autorotate
|
|
|
|
if _fullscreenPlayerPresented {
|
|
|
|
_playerViewController?.autorotate = autorotate
|
|
|
|
}
|
|
|
|
}
|
2022-11-09 09:26:12 -07:00
|
|
|
|
2022-05-19 07:29:25 -06:00
|
|
|
@objc
|
2023-12-07 00:47:40 -07:00
|
|
|
func setFullscreenOrientation(_ orientation: String?) {
|
2022-05-19 07:29:25 -06:00
|
|
|
_fullscreenOrientation = orientation
|
|
|
|
if _fullscreenPlayerPresented {
|
|
|
|
_playerViewController?.preferredOrientation = orientation
|
|
|
|
}
|
|
|
|
}
|
2022-11-09 09:26:12 -07:00
|
|
|
|
2022-05-19 07:29:25 -06:00
|
|
|
func usePlayerViewController() {
|
2024-01-04 12:16:23 -07:00
|
|
|
guard let _player, let _playerItem else { return }
|
2022-11-09 09:26:12 -07:00
|
|
|
|
2022-05-19 07:29:25 -06:00
|
|
|
if _playerViewController == nil {
|
2023-12-07 00:47:40 -07:00
|
|
|
_playerViewController = createPlayerViewController(player: _player, withPlayerItem: _playerItem)
|
2022-05-19 07:29:25 -06:00
|
|
|
}
|
|
|
|
// to prevent video from being animated when resizeMode is 'cover'
|
|
|
|
// resize mode must be set before subview is added
|
|
|
|
setResizeMode(_resizeMode)
|
2022-11-09 09:26:12 -07:00
|
|
|
|
2024-01-04 12:16:23 -07:00
|
|
|
guard let _playerViewController else { return }
|
2022-11-09 09:26:12 -07:00
|
|
|
|
2022-05-19 07:29:25 -06:00
|
|
|
if _controls {
|
2023-12-07 00:47:40 -07:00
|
|
|
let viewController: UIViewController! = self.reactViewController()
|
2022-12-07 04:16:03 -07:00
|
|
|
viewController?.addChild(_playerViewController)
|
2022-05-19 07:29:25 -06:00
|
|
|
self.addSubview(_playerViewController.view)
|
|
|
|
}
|
2022-11-09 09:26:12 -07:00
|
|
|
|
2022-05-19 07:29:25 -06:00
|
|
|
_playerObserver.playerViewController = _playerViewController
|
|
|
|
}
|
2022-11-09 09:26:12 -07:00
|
|
|
|
2023-12-07 00:47:40 -07:00
|
|
|
func createPlayerViewController(player: AVPlayer, withPlayerItem _: AVPlayerItem) -> RCTVideoPlayerViewController {
|
2022-05-19 07:29:25 -06:00
|
|
|
let viewController = RCTVideoPlayerViewController()
|
2023-01-28 06:40:45 -07:00
|
|
|
viewController.showsPlaybackControls = self._controls
|
2024-05-20 05:09:58 -06:00
|
|
|
#if !os(tvOS)
|
|
|
|
viewController.updatesNowPlayingInfoCenter = false
|
|
|
|
#endif
|
2022-05-19 07:29:25 -06:00
|
|
|
viewController.rctDelegate = self
|
|
|
|
viewController.preferredOrientation = _fullscreenOrientation
|
2022-11-09 09:26:12 -07:00
|
|
|
|
2022-05-19 07:29:25 -06:00
|
|
|
viewController.view.frame = self.bounds
|
|
|
|
viewController.player = player
|
2023-10-06 10:42:53 -06:00
|
|
|
if #available(tvOS 14.0, *) {
|
2024-04-05 02:37:00 -06:00
|
|
|
viewController.allowsPictureInPicturePlayback = _pictureInPictureEnabled
|
2023-10-06 10:42:53 -06:00
|
|
|
}
|
2022-05-19 07:29:25 -06:00
|
|
|
return viewController
|
|
|
|
}
|
2022-11-09 09:26:12 -07:00
|
|
|
|
2022-05-19 07:29:25 -06:00
|
|
|
func usePlayerLayer() {
|
2024-01-04 12:16:23 -07:00
|
|
|
if let _player {
|
2022-05-19 07:29:25 -06:00
|
|
|
_playerLayer = AVPlayerLayer(player: _player)
|
|
|
|
_playerLayer?.frame = self.bounds
|
|
|
|
_playerLayer?.needsDisplayOnBoundsChange = true
|
2022-11-09 09:26:12 -07:00
|
|
|
|
2022-05-19 07:29:25 -06:00
|
|
|
// to prevent video from being animated when resizeMode is 'cover'
|
|
|
|
// resize mode must be set before layer is added
|
|
|
|
setResizeMode(_resizeMode)
|
|
|
|
_playerObserver.playerLayer = _playerLayer
|
2022-11-09 09:26:12 -07:00
|
|
|
|
2024-01-04 12:16:23 -07:00
|
|
|
if let _playerLayer {
|
2022-05-19 07:29:25 -06:00
|
|
|
self.layer.addSublayer(_playerLayer)
|
|
|
|
}
|
|
|
|
self.layer.needsDisplayOnBoundsChange = true
|
2023-12-07 00:47:40 -07:00
|
|
|
#if os(iOS)
|
2024-04-05 02:37:00 -06:00
|
|
|
if _pictureInPictureEnabled {
|
|
|
|
_pip?.setupPipController(_playerLayer)
|
|
|
|
}
|
2023-12-07 00:47:40 -07:00
|
|
|
#endif
|
2022-05-19 07:29:25 -06:00
|
|
|
}
|
|
|
|
}
|
2022-11-09 09:26:12 -07:00
|
|
|
|
2022-05-19 07:29:25 -06:00
|
|
|
@objc
|
2023-12-07 00:47:40 -07:00
|
|
|
func setControls(_ controls: Bool) {
|
|
|
|
if _controls != controls || ((_playerLayer == nil) && (_playerViewController == nil)) {
|
2022-05-19 07:29:25 -06:00
|
|
|
_controls = controls
|
2023-12-07 00:47:40 -07:00
|
|
|
if _controls {
|
2022-05-19 07:29:25 -06:00
|
|
|
self.removePlayerLayer()
|
|
|
|
self.usePlayerViewController()
|
2023-12-07 00:47:40 -07:00
|
|
|
} else {
|
2022-05-19 07:29:25 -06:00
|
|
|
_playerViewController?.view.removeFromSuperview()
|
2022-11-04 01:53:02 -06:00
|
|
|
_playerViewController?.removeFromParent()
|
2022-05-19 07:29:25 -06:00
|
|
|
_playerViewController = nil
|
|
|
|
_playerObserver.playerViewController = nil
|
|
|
|
self.usePlayerLayer()
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2022-11-09 09:26:12 -07:00
|
|
|
|
2024-05-07 04:30:57 -06:00
|
|
|
@objc
|
|
|
|
func setShowNotificationControls(_ showNotificationControls: Bool) {
|
2024-06-12 03:54:22 -06:00
|
|
|
_showNotificationControls = showNotificationControls
|
|
|
|
|
2024-05-07 04:30:57 -06:00
|
|
|
guard let player = _player else {
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
if showNotificationControls {
|
|
|
|
NowPlayingInfoCenterManager.shared.registerPlayer(player: player)
|
|
|
|
} else {
|
|
|
|
NowPlayingInfoCenterManager.shared.removePlayer(player: player)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-05-19 07:29:25 -06:00
|
|
|
@objc
|
2023-12-07 00:47:40 -07:00
|
|
|
func setProgressUpdateInterval(_ progressUpdateInterval: Float) {
|
2022-05-19 07:29:25 -06:00
|
|
|
_playerObserver.replaceTimeObserverIfSet(Float64(progressUpdateInterval))
|
|
|
|
}
|
2022-11-09 09:26:12 -07:00
|
|
|
|
2022-05-19 07:29:25 -06:00
|
|
|
func removePlayerLayer() {
|
|
|
|
_playerLayer?.removeFromSuperlayer()
|
|
|
|
_playerLayer = nil
|
|
|
|
_playerObserver.playerLayer = nil
|
|
|
|
}
|
2022-11-09 09:26:12 -07:00
|
|
|
|
2024-03-14 04:29:50 -06:00
|
|
|
@objc
|
|
|
|
func setSubtitleStyle(_ style: [String: Any]) {
|
|
|
|
let subtitleStyle = SubtitleStyle.parse(from: style)
|
|
|
|
_playerObserver.subtitleStyle = subtitleStyle
|
|
|
|
}
|
|
|
|
|
2022-05-19 07:29:25 -06:00
|
|
|
// MARK: - RCTVideoPlayerViewControllerDelegate
|
2022-11-09 09:26:12 -07:00
|
|
|
|
2023-12-07 00:47:40 -07:00
|
|
|
func videoPlayerViewControllerWillDismiss(playerViewController: AVPlayerViewController) {
|
|
|
|
if _playerViewController == playerViewController
|
|
|
|
&& _fullscreenPlayerPresented,
|
2024-01-04 12:16:23 -07:00
|
|
|
let onVideoFullscreenPlayerWillDismiss {
|
2022-05-19 07:29:25 -06:00
|
|
|
_playerObserver.removePlayerViewControllerObservers()
|
|
|
|
onVideoFullscreenPlayerWillDismiss(["target": reactTag as Any])
|
|
|
|
}
|
|
|
|
}
|
2022-11-09 09:26:12 -07:00
|
|
|
|
2023-12-07 00:47:40 -07:00
|
|
|
func videoPlayerViewControllerDidDismiss(playerViewController: AVPlayerViewController) {
|
2022-05-19 07:29:25 -06:00
|
|
|
if _playerViewController == playerViewController && _fullscreenPlayerPresented {
|
|
|
|
_fullscreenPlayerPresented = false
|
|
|
|
_presentingViewController = nil
|
|
|
|
_playerViewController = nil
|
|
|
|
_playerObserver.playerViewController = nil
|
|
|
|
self.applyModifiers()
|
2022-11-09 09:26:12 -07:00
|
|
|
|
2022-05-19 07:29:25 -06:00
|
|
|
onVideoFullscreenPlayerDidDismiss?(["target": reactTag as Any])
|
|
|
|
}
|
|
|
|
}
|
2022-11-09 09:26:12 -07:00
|
|
|
|
2022-05-19 07:29:25 -06:00
|
|
|
@objc
|
2023-12-07 00:47:40 -07:00
|
|
|
func setFilter(_ filterName: String!) {
|
2022-05-19 07:29:25 -06:00
|
|
|
_filterName = filterName
|
2022-11-09 09:26:12 -07:00
|
|
|
|
2022-05-19 07:29:25 -06:00
|
|
|
if !_filterEnabled {
|
|
|
|
return
|
|
|
|
} else if let uri = _source?.uri, uri.contains("m3u8") {
|
|
|
|
return // filters don't work for HLS... return
|
|
|
|
} else if _playerItem?.asset == nil {
|
|
|
|
return
|
|
|
|
}
|
2022-11-09 09:26:12 -07:00
|
|
|
|
2023-12-07 00:47:40 -07:00
|
|
|
let filter: CIFilter! = CIFilter(name: filterName)
|
2024-04-04 05:23:44 -06:00
|
|
|
Task {
|
|
|
|
let composition = await RCTVideoUtils.generateVideoComposition(asset: _playerItem!.asset, filter: filter)
|
|
|
|
self._playerItem?.videoComposition = composition
|
2022-05-19 07:29:25 -06:00
|
|
|
}
|
|
|
|
}
|
2022-11-09 09:26:12 -07:00
|
|
|
|
2022-05-19 07:29:25 -06:00
|
|
|
@objc
|
2023-12-07 00:47:40 -07:00
|
|
|
func setFilterEnabled(_ filterEnabled: Bool) {
|
2022-05-19 07:29:25 -06:00
|
|
|
_filterEnabled = filterEnabled
|
|
|
|
}
|
2022-11-09 09:26:12 -07:00
|
|
|
|
2022-11-17 04:01:29 -07:00
|
|
|
// MARK: - RCTIMAAdsManager
|
2022-11-17 04:12:50 -07:00
|
|
|
|
2024-08-29 04:30:05 -06:00
|
|
|
func getAdLanguage() -> String? {
|
|
|
|
return _adLanguage
|
|
|
|
}
|
|
|
|
|
2022-11-17 04:01:29 -07:00
|
|
|
func getAdTagUrl() -> String? {
|
|
|
|
return _adTagUrl
|
|
|
|
}
|
|
|
|
|
2022-11-10 03:43:50 -07:00
|
|
|
@objc
|
2023-12-07 00:47:40 -07:00
|
|
|
func setAdTagUrl(_ adTagUrl: String!) {
|
2022-11-10 03:43:50 -07:00
|
|
|
_adTagUrl = adTagUrl
|
|
|
|
}
|
2023-12-07 00:47:40 -07:00
|
|
|
|
|
|
|
#if USE_GOOGLE_IMA
|
|
|
|
func getContentPlayhead() -> IMAAVPlayerContentPlayhead? {
|
|
|
|
return _contentPlayhead
|
|
|
|
}
|
|
|
|
#endif
|
|
|
|
func setAdPlaying(_ adPlaying: Bool) {
|
2022-11-17 04:01:29 -07:00
|
|
|
_adPlaying = adPlaying
|
|
|
|
}
|
2022-11-10 03:43:50 -07:00
|
|
|
|
2022-05-19 07:29:25 -06:00
|
|
|
// MARK: - React View Management
|
2022-11-09 09:26:12 -07:00
|
|
|
|
2023-12-07 00:47:40 -07:00
|
|
|
func insertReactSubview(view: UIView!, atIndex: Int) {
|
2022-05-19 07:29:25 -06:00
|
|
|
if _controls {
|
|
|
|
view.frame = self.bounds
|
2023-12-07 00:47:40 -07:00
|
|
|
_playerViewController?.contentOverlayView?.insertSubview(view, at: atIndex)
|
2022-05-19 07:29:25 -06:00
|
|
|
} else {
|
|
|
|
RCTLogError("video cannot have any subviews")
|
|
|
|
}
|
|
|
|
return
|
|
|
|
}
|
2022-11-09 09:26:12 -07:00
|
|
|
|
2023-12-07 00:47:40 -07:00
|
|
|
func removeReactSubview(subview: UIView!) {
|
2022-05-19 07:29:25 -06:00
|
|
|
if _controls {
|
|
|
|
subview.removeFromSuperview()
|
|
|
|
} else {
|
|
|
|
RCTLog("video cannot have any subviews")
|
|
|
|
}
|
|
|
|
return
|
|
|
|
}
|
2022-11-09 09:26:12 -07:00
|
|
|
|
2022-05-19 07:29:25 -06:00
|
|
|
override func layoutSubviews() {
|
|
|
|
super.layoutSubviews()
|
2024-01-04 12:16:23 -07:00
|
|
|
if _controls, let _playerViewController {
|
2022-05-19 07:29:25 -06:00
|
|
|
_playerViewController.view.frame = bounds
|
2022-11-09 09:26:12 -07:00
|
|
|
|
2022-05-19 07:29:25 -06:00
|
|
|
// also adjust all subviews of contentOverlayView
|
|
|
|
for subview in _playerViewController.contentOverlayView?.subviews ?? [] {
|
|
|
|
subview.frame = bounds
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
CATransaction.begin()
|
|
|
|
CATransaction.setAnimationDuration(0)
|
|
|
|
_playerLayer?.frame = bounds
|
|
|
|
CATransaction.commit()
|
|
|
|
}
|
|
|
|
}
|
2022-11-09 09:26:12 -07:00
|
|
|
|
2022-05-19 07:29:25 -06:00
|
|
|
// MARK: - Lifecycle
|
2022-11-09 09:26:12 -07:00
|
|
|
|
2022-05-19 07:29:25 -06:00
|
|
|
override func removeFromSuperview() {
|
2024-06-07 05:54:14 -06:00
|
|
|
self._player?.replaceCurrentItem(with: nil)
|
2024-05-07 04:30:57 -06:00
|
|
|
if let player = _player {
|
|
|
|
player.pause()
|
|
|
|
NowPlayingInfoCenterManager.shared.removePlayer(player: player)
|
|
|
|
}
|
2024-06-07 05:54:14 -06:00
|
|
|
_playerItem = nil
|
|
|
|
_source = nil
|
|
|
|
_chapters = nil
|
2024-09-02 07:40:38 -06:00
|
|
|
_textTracks = []
|
2024-06-07 05:54:14 -06:00
|
|
|
_selectedTextTrackCriteria = nil
|
|
|
|
_selectedAudioTrackCriteria = nil
|
|
|
|
_presentingViewController = nil
|
2024-05-07 04:30:57 -06:00
|
|
|
|
2024-06-25 00:55:32 -06:00
|
|
|
ReactNativeVideoManager.shared.onInstanceRemoved(id: instanceId, player: _player)
|
2022-05-19 07:29:25 -06:00
|
|
|
_player = nil
|
2023-01-23 08:49:46 -07:00
|
|
|
_resouceLoaderDelegate = nil
|
2022-05-19 07:29:25 -06:00
|
|
|
_playerObserver.clearPlayer()
|
2022-11-09 09:26:12 -07:00
|
|
|
|
2022-05-19 07:29:25 -06:00
|
|
|
self.removePlayerLayer()
|
2022-11-09 09:26:12 -07:00
|
|
|
|
2024-01-04 12:16:23 -07:00
|
|
|
if let _playerViewController {
|
2022-05-19 07:29:25 -06:00
|
|
|
_playerViewController.view.removeFromSuperview()
|
2022-11-04 01:53:02 -06:00
|
|
|
_playerViewController.removeFromParent()
|
2022-05-19 07:29:25 -06:00
|
|
|
_playerViewController.rctDelegate = nil
|
|
|
|
_playerViewController.player = nil
|
|
|
|
self._playerViewController = nil
|
|
|
|
_playerObserver.playerViewController = nil
|
|
|
|
}
|
2022-11-09 09:26:12 -07:00
|
|
|
|
2022-05-19 07:29:25 -06:00
|
|
|
_eventDispatcher = nil
|
2023-12-07 00:47:40 -07:00
|
|
|
// swiftlint:disable:next notification_center_detachment
|
2022-05-19 07:29:25 -06:00
|
|
|
NotificationCenter.default.removeObserver(self)
|
2022-11-09 09:26:12 -07:00
|
|
|
|
2022-05-19 07:29:25 -06:00
|
|
|
super.removeFromSuperview()
|
|
|
|
}
|
2022-11-09 09:26:12 -07:00
|
|
|
|
2022-05-19 07:29:25 -06:00
|
|
|
// MARK: - Export
|
2022-11-09 09:26:12 -07:00
|
|
|
|
2022-05-19 07:29:25 -06:00
|
|
|
@objc
|
2024-07-12 02:27:42 -06:00
|
|
|
func save(_ options: NSDictionary!, _ resolve: @escaping RCTPromiseResolveBlock, _ reject: @escaping RCTPromiseRejectBlock) {
|
2022-05-19 07:29:25 -06:00
|
|
|
RCTVideoSave.save(
|
2023-12-07 00:47:40 -07:00
|
|
|
options: options,
|
|
|
|
resolve: resolve,
|
|
|
|
reject: reject,
|
|
|
|
playerItem: _playerItem
|
2022-05-19 07:29:25 -06:00
|
|
|
)
|
|
|
|
}
|
2022-11-09 09:26:12 -07:00
|
|
|
|
2023-12-07 00:47:40 -07:00
|
|
|
func setLicenseResult(_ license: String!, _ licenseUrl: String!) {
|
2023-10-05 13:37:28 -06:00
|
|
|
_resouceLoaderDelegate?.setLicenseResult(license, licenseUrl)
|
2022-05-19 07:29:25 -06:00
|
|
|
}
|
2022-11-09 09:26:12 -07:00
|
|
|
|
2023-12-07 00:47:40 -07:00
|
|
|
func setLicenseResultError(_ error: String!, _ licenseUrl: String!) {
|
2023-10-05 13:37:28 -06:00
|
|
|
_resouceLoaderDelegate?.setLicenseResultError(error, licenseUrl)
|
2022-05-19 07:29:25 -06:00
|
|
|
}
|
2022-11-09 09:26:12 -07:00
|
|
|
|
2022-05-19 07:29:25 -06:00
|
|
|
// MARK: - RCTPlayerObserverHandler
|
2022-11-09 09:26:12 -07:00
|
|
|
|
2023-12-07 00:47:40 -07:00
|
|
|
func handleTimeUpdate(time _: CMTime) {
|
2022-05-19 07:29:25 -06:00
|
|
|
sendProgressUpdate()
|
|
|
|
}
|
2022-11-09 09:26:12 -07:00
|
|
|
|
2023-12-07 00:47:40 -07:00
|
|
|
func handleReadyForDisplay(changeObject _: Any, change _: NSKeyValueObservedChange<Bool>) {
|
2024-05-13 13:32:51 -06:00
|
|
|
if _isBuffering {
|
|
|
|
_isBuffering = false
|
|
|
|
}
|
2022-05-19 07:29:25 -06:00
|
|
|
onReadyForDisplay?([
|
2023-12-07 00:47:40 -07:00
|
|
|
"target": reactTag,
|
2022-05-19 07:29:25 -06:00
|
|
|
])
|
|
|
|
}
|
2022-11-09 09:26:12 -07:00
|
|
|
|
2022-05-19 07:29:25 -06:00
|
|
|
// When timeMetadata is read the event onTimedMetadata is triggered
|
2024-01-04 06:14:51 -07:00
|
|
|
func handleTimeMetadataChange(timedMetadata: [AVMetadataItem]) {
|
2024-05-07 03:06:12 -06:00
|
|
|
guard onTimedMetadata != nil else { return }
|
2024-05-08 08:07:32 -06:00
|
|
|
|
2023-12-07 00:47:40 -07:00
|
|
|
var metadata: [[String: String?]?] = []
|
2024-01-04 06:14:51 -07:00
|
|
|
for item in timedMetadata {
|
2022-05-19 07:29:25 -06:00
|
|
|
let value = item.value as? String
|
|
|
|
let identifier = item.identifier?.rawValue
|
2022-11-09 09:26:12 -07:00
|
|
|
|
2024-01-04 12:16:23 -07:00
|
|
|
if let value {
|
2023-12-07 00:47:40 -07:00
|
|
|
metadata.append(["value": value, "identifier": identifier])
|
2022-05-19 07:29:25 -06:00
|
|
|
}
|
|
|
|
}
|
2022-11-09 09:26:12 -07:00
|
|
|
|
2022-05-19 07:29:25 -06:00
|
|
|
onTimedMetadata?([
|
|
|
|
"target": reactTag,
|
2023-12-07 00:47:40 -07:00
|
|
|
"metadata": metadata,
|
2022-05-19 07:29:25 -06:00
|
|
|
])
|
|
|
|
}
|
2022-11-09 09:26:12 -07:00
|
|
|
|
2022-05-19 07:29:25 -06:00
|
|
|
// Handle player item status change.
|
2023-12-07 00:47:40 -07:00
|
|
|
func handlePlayerItemStatusChange(playerItem _: AVPlayerItem, change _: NSKeyValueObservedChange<AVPlayerItem.Status>) {
|
2024-01-04 12:16:23 -07:00
|
|
|
guard let _playerItem else {
|
2022-05-19 07:29:25 -06:00
|
|
|
return
|
|
|
|
}
|
2022-11-09 09:26:12 -07:00
|
|
|
|
2022-05-19 07:29:25 -06:00
|
|
|
if _playerItem.status == .readyToPlay {
|
|
|
|
handleReadyToPlay()
|
|
|
|
} else if _playerItem.status == .failed {
|
|
|
|
handlePlaybackFailed()
|
|
|
|
}
|
|
|
|
}
|
2022-11-09 09:26:12 -07:00
|
|
|
|
2024-09-02 09:01:39 -06:00
|
|
|
func extractJsonWithIndex(from tracks: [TextTrack]) -> [NSDictionary]? {
|
|
|
|
if tracks.isEmpty {
|
|
|
|
// No tracks, need to return nil to handle
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
// Map each enumerated pair to include the index in the json dictionary
|
|
|
|
let mappedTracks = tracks.enumerated().compactMap { index, track -> NSDictionary? in
|
|
|
|
guard let json = track.json?.mutableCopy() as? NSMutableDictionary else { return nil }
|
|
|
|
json["index"] = index // Insert the index into the json dictionary
|
|
|
|
return json
|
|
|
|
}
|
|
|
|
return mappedTracks
|
|
|
|
}
|
|
|
|
|
2022-05-19 07:29:25 -06:00
|
|
|
func handleReadyToPlay() {
|
2024-01-04 12:16:23 -07:00
|
|
|
guard let _playerItem else { return }
|
2022-11-09 09:26:12 -07:00
|
|
|
|
2024-04-04 05:23:44 -06:00
|
|
|
Task {
|
2024-01-06 12:06:53 -07:00
|
|
|
if self._pendingSeek {
|
2024-07-12 02:27:42 -06:00
|
|
|
self.setSeek(NSNumber(value: self._pendingSeekTime), NSNumber(value: 100))
|
2024-01-06 12:06:53 -07:00
|
|
|
self._pendingSeek = false
|
|
|
|
}
|
2022-11-09 09:26:12 -07:00
|
|
|
|
2024-01-06 12:06:53 -07:00
|
|
|
if self._startPosition >= 0 {
|
2024-07-12 02:27:42 -06:00
|
|
|
self.setSeek(NSNumber(value: self._startPosition), NSNumber(value: 100))
|
2024-01-06 12:06:53 -07:00
|
|
|
self._startPosition = -1
|
|
|
|
}
|
2023-11-24 04:52:46 -07:00
|
|
|
|
2024-05-08 08:07:32 -06:00
|
|
|
if onVideoLoad != nil, self._videoLoadStarted {
|
|
|
|
var duration = Float(CMTimeGetSeconds(_playerItem.asset.duration))
|
|
|
|
|
|
|
|
if duration.isNaN || duration == 0 {
|
|
|
|
// This is a safety check for live video.
|
|
|
|
// AVPlayer report a 0 duration
|
|
|
|
duration = RCTVideoUtils.calculateSeekableDuration(_player).floatValue
|
|
|
|
if duration.isNaN {
|
|
|
|
duration = 0
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
var width: Float = 0
|
|
|
|
var height: Float = 0
|
|
|
|
var orientation = "undefined"
|
|
|
|
|
|
|
|
let tracks = await RCTVideoAssetsUtils.getTracks(asset: _playerItem.asset, withMediaType: .video)
|
2024-05-22 14:54:00 -06:00
|
|
|
var presentationSize = _playerItem.presentationSize
|
|
|
|
if presentationSize.height != 0.0 {
|
|
|
|
width = Float(presentationSize.width)
|
|
|
|
height = Float(presentationSize.height)
|
|
|
|
} else if let videoTrack = tracks?.first {
|
|
|
|
let naturalSize = videoTrack.naturalSize
|
|
|
|
width = Float(naturalSize.width)
|
|
|
|
height = Float(naturalSize.height)
|
2024-05-08 08:07:32 -06:00
|
|
|
}
|
|
|
|
orientation = width > height ? "landscape" : width == height ? "square" : "portrait"
|
|
|
|
|
2024-04-04 05:23:44 -06:00
|
|
|
let audioTracks = await RCTVideoUtils.getAudioTrackInfo(self._player)
|
|
|
|
let textTracks = await RCTVideoUtils.getTextTrackInfo(self._player)
|
|
|
|
self.onVideoLoad?(["duration": NSNumber(value: duration),
|
|
|
|
"currentTime": NSNumber(value: Float(CMTimeGetSeconds(_playerItem.currentTime()))),
|
|
|
|
"canPlayReverse": NSNumber(value: _playerItem.canPlayReverse),
|
|
|
|
"canPlayFastForward": NSNumber(value: _playerItem.canPlayFastForward),
|
|
|
|
"canPlaySlowForward": NSNumber(value: _playerItem.canPlaySlowForward),
|
|
|
|
"canPlaySlowReverse": NSNumber(value: _playerItem.canPlaySlowReverse),
|
|
|
|
"canStepBackward": NSNumber(value: _playerItem.canStepBackward),
|
|
|
|
"canStepForward": NSNumber(value: _playerItem.canStepForward),
|
|
|
|
"naturalSize": [
|
2024-04-26 02:02:13 -06:00
|
|
|
"width": width,
|
|
|
|
"height": height,
|
2024-04-04 05:23:44 -06:00
|
|
|
"orientation": orientation,
|
|
|
|
],
|
|
|
|
"audioTracks": audioTracks,
|
2024-09-02 09:01:39 -06:00
|
|
|
"textTracks": extractJsonWithIndex(from: _textTracks) ?? textTracks.map(\.json),
|
2024-04-04 05:23:44 -06:00
|
|
|
"target": self.reactTag as Any])
|
2024-01-06 12:06:53 -07:00
|
|
|
}
|
2024-04-04 05:23:44 -06:00
|
|
|
|
2024-01-06 12:06:53 -07:00
|
|
|
self._videoLoadStarted = false
|
|
|
|
self._playerObserver.attachPlayerEventListeners()
|
|
|
|
self.applyModifiers()
|
2022-05-19 07:29:25 -06:00
|
|
|
}
|
|
|
|
}
|
2022-11-09 09:26:12 -07:00
|
|
|
|
2022-05-19 07:29:25 -06:00
|
|
|
func handlePlaybackFailed() {
|
2024-05-07 04:30:57 -06:00
|
|
|
if let player = _player {
|
|
|
|
NowPlayingInfoCenterManager.shared.removePlayer(player: player)
|
|
|
|
}
|
|
|
|
|
2024-01-04 12:16:23 -07:00
|
|
|
guard let _playerItem else { return }
|
2022-05-19 07:29:25 -06:00
|
|
|
onVideoError?(
|
|
|
|
[
|
|
|
|
"error": [
|
|
|
|
"code": NSNumber(value: (_playerItem.error! as NSError).code),
|
|
|
|
"localizedDescription": _playerItem.error?.localizedDescription == nil ? "" : _playerItem.error?.localizedDescription,
|
2023-12-07 00:47:40 -07:00
|
|
|
"localizedFailureReason": ((_playerItem.error! as NSError).localizedFailureReason == nil ?
|
|
|
|
"" : (_playerItem.error! as NSError).localizedFailureReason) ?? "",
|
|
|
|
"localizedRecoverySuggestion": ((_playerItem.error! as NSError).localizedRecoverySuggestion == nil ?
|
|
|
|
"" : (_playerItem.error! as NSError).localizedRecoverySuggestion) ?? "",
|
|
|
|
"domain": (_playerItem.error as! NSError).domain,
|
2022-05-19 07:29:25 -06:00
|
|
|
],
|
2023-12-07 00:47:40 -07:00
|
|
|
"target": reactTag,
|
|
|
|
]
|
|
|
|
)
|
2022-05-19 07:29:25 -06:00
|
|
|
}
|
2022-11-09 09:26:12 -07:00
|
|
|
|
2023-12-07 00:47:40 -07:00
|
|
|
func handlePlaybackBufferKeyEmpty(playerItem _: AVPlayerItem, change _: NSKeyValueObservedChange<Bool>) {
|
2024-05-13 13:32:51 -06:00
|
|
|
if !_isBuffering {
|
|
|
|
_isBuffering = true
|
|
|
|
}
|
2022-05-19 07:29:25 -06:00
|
|
|
}
|
2022-11-09 09:26:12 -07:00
|
|
|
|
2022-05-19 07:29:25 -06:00
|
|
|
// Continue playing (or not if paused) after being paused due to hitting an unbuffered zone.
|
2023-12-07 00:47:40 -07:00
|
|
|
func handlePlaybackLikelyToKeepUp(playerItem _: AVPlayerItem, change _: NSKeyValueObservedChange<Bool>) {
|
2024-05-13 13:32:51 -06:00
|
|
|
if _isBuffering {
|
|
|
|
_isBuffering = false
|
|
|
|
}
|
2022-05-19 07:29:25 -06:00
|
|
|
}
|
2022-11-09 09:26:12 -07:00
|
|
|
|
2024-04-18 02:11:15 -06:00
|
|
|
func handleTimeControlStatusChange(player: AVPlayer, change: NSKeyValueObservedChange<AVPlayer.TimeControlStatus>) {
|
|
|
|
if player.timeControlStatus == change.oldValue && change.oldValue != nil {
|
|
|
|
return
|
|
|
|
}
|
|
|
|
guard [.paused, .playing].contains(player.timeControlStatus) else {
|
|
|
|
return
|
|
|
|
}
|
|
|
|
let isPlaying = player.timeControlStatus == .playing
|
|
|
|
|
2024-05-28 13:04:55 -06:00
|
|
|
guard _isPlaying != isPlaying else { return }
|
2024-04-18 02:11:15 -06:00
|
|
|
_isPlaying = isPlaying
|
2024-07-11 02:08:36 -06:00
|
|
|
onVideoPlaybackStateChanged?(["isPlaying": isPlaying, "isSeeking": self._pendingSeek == true, "target": reactTag as Any])
|
2024-04-18 02:11:15 -06:00
|
|
|
}
|
|
|
|
|
2022-05-19 07:29:25 -06:00
|
|
|
func handlePlaybackRateChange(player: AVPlayer, change: NSKeyValueObservedChange<Float>) {
|
2024-01-04 12:16:23 -07:00
|
|
|
guard let _player else { return }
|
2023-11-17 00:19:39 -07:00
|
|
|
|
2023-12-07 00:47:40 -07:00
|
|
|
if player.rate == change.oldValue && change.oldValue != nil {
|
2023-10-24 14:10:29 -06:00
|
|
|
return
|
|
|
|
}
|
2023-11-17 00:19:39 -07:00
|
|
|
|
2022-05-19 07:29:25 -06:00
|
|
|
onPlaybackRateChange?(["playbackRate": NSNumber(value: _player.rate),
|
|
|
|
"target": reactTag as Any])
|
2023-11-17 00:19:39 -07:00
|
|
|
|
2022-05-19 07:29:25 -06:00
|
|
|
if _playbackStalled && _player.rate > 0 {
|
|
|
|
onPlaybackResume?(["playbackRate": NSNumber(value: _player.rate),
|
|
|
|
"target": reactTag as Any])
|
|
|
|
_playbackStalled = false
|
|
|
|
}
|
|
|
|
}
|
2023-11-17 00:19:39 -07:00
|
|
|
|
2023-11-04 11:11:54 -06:00
|
|
|
func handleVolumeChange(player: AVPlayer, change: NSKeyValueObservedChange<Float>) {
|
2024-05-08 08:07:32 -06:00
|
|
|
guard let _player, onVolumeChange != nil else { return }
|
2023-11-17 00:19:39 -07:00
|
|
|
|
2023-12-07 00:47:40 -07:00
|
|
|
if player.rate == change.oldValue && change.oldValue != nil {
|
|
|
|
return
|
2023-11-04 11:11:54 -06:00
|
|
|
}
|
2023-11-17 00:19:39 -07:00
|
|
|
|
2023-11-04 11:11:54 -06:00
|
|
|
onVolumeChange?(["volume": NSNumber(value: _player.volume),
|
|
|
|
"target": reactTag as Any])
|
|
|
|
}
|
2022-11-09 09:26:12 -07:00
|
|
|
|
2023-12-07 00:47:40 -07:00
|
|
|
func handleExternalPlaybackActiveChange(player _: AVPlayer, change _: NSKeyValueObservedChange<Bool>) {
|
2024-01-15 00:04:29 -07:00
|
|
|
#if !os(visionOS)
|
2024-05-28 13:04:55 -06:00
|
|
|
guard let _player else { return }
|
|
|
|
if !_playInBackground && UIApplication.shared.applicationState == .background {
|
|
|
|
_playerLayer?.player = nil
|
|
|
|
_playerViewController?.player = nil
|
|
|
|
}
|
|
|
|
guard onVideoExternalPlaybackChange != nil else { return }
|
2024-01-15 00:04:29 -07:00
|
|
|
onVideoExternalPlaybackChange?(["isExternalPlaybackActive": NSNumber(value: _player.isExternalPlaybackActive),
|
|
|
|
"target": reactTag as Any])
|
|
|
|
#endif
|
2022-05-19 07:29:25 -06:00
|
|
|
}
|
2022-11-09 09:26:12 -07:00
|
|
|
|
2023-12-07 00:47:40 -07:00
|
|
|
func handleViewControllerOverlayViewFrameChange(overlayView _: UIView, change: NSKeyValueObservedChange<CGRect>) {
|
2022-05-19 07:29:25 -06:00
|
|
|
let oldRect = change.oldValue
|
|
|
|
let newRect = change.newValue
|
2023-12-28 04:58:25 -07:00
|
|
|
|
|
|
|
guard let bounds = RCTVideoUtils.getCurrentWindow()?.bounds else { return }
|
|
|
|
|
2022-05-19 07:29:25 -06:00
|
|
|
if !oldRect!.equalTo(newRect!) {
|
2024-05-07 09:25:09 -06:00
|
|
|
// https://github.com/TheWidlarzGroup/react-native-video/issues/3085#issuecomment-1557293391
|
2023-12-28 04:58:25 -07:00
|
|
|
if newRect!.equalTo(bounds) {
|
2023-01-28 06:29:00 -07:00
|
|
|
RCTLog("in fullscreen")
|
2023-12-07 00:47:40 -07:00
|
|
|
if !_fullscreenUncontrolPlayerPresented {
|
|
|
|
_fullscreenUncontrolPlayerPresented = true
|
2022-11-09 09:26:12 -07:00
|
|
|
|
2023-07-06 03:16:49 -06:00
|
|
|
self.onVideoFullscreenPlayerWillPresent?(["target": self.reactTag as Any])
|
|
|
|
self.onVideoFullscreenPlayerDidPresent?(["target": self.reactTag as Any])
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
NSLog("not fullscreen")
|
2023-12-07 00:47:40 -07:00
|
|
|
if _fullscreenUncontrolPlayerPresented {
|
|
|
|
_fullscreenUncontrolPlayerPresented = false
|
2023-07-06 03:16:49 -06:00
|
|
|
|
|
|
|
self.onVideoFullscreenPlayerWillDismiss?(["target": self.reactTag as Any])
|
|
|
|
self.onVideoFullscreenPlayerDidDismiss?(["target": self.reactTag as Any])
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2023-12-28 04:58:25 -07:00
|
|
|
self.reactViewController().view.frame = bounds
|
2023-07-06 03:16:49 -06:00
|
|
|
self.reactViewController().view.setNeedsLayout()
|
2022-05-19 07:29:25 -06:00
|
|
|
}
|
|
|
|
}
|
2022-11-09 09:26:12 -07:00
|
|
|
|
2023-12-07 00:47:40 -07:00
|
|
|
@objc
|
|
|
|
func handleDidFailToFinishPlaying(notification: NSNotification!) {
|
2024-05-08 08:07:32 -06:00
|
|
|
guard onVideoError != nil else { return }
|
|
|
|
|
2023-12-07 00:47:40 -07:00
|
|
|
let error: NSError! = notification.userInfo?[AVPlayerItemFailedToPlayToEndTimeErrorKey] as? NSError
|
2022-05-19 07:29:25 -06:00
|
|
|
onVideoError?(
|
|
|
|
[
|
|
|
|
"error": [
|
|
|
|
"code": NSNumber(value: (error as NSError).code),
|
|
|
|
"localizedDescription": error.localizedDescription ?? "",
|
|
|
|
"localizedFailureReason": (error as NSError).localizedFailureReason ?? "",
|
|
|
|
"localizedRecoverySuggestion": (error as NSError).localizedRecoverySuggestion ?? "",
|
2023-12-07 00:47:40 -07:00
|
|
|
"domain": (error as NSError).domain,
|
2022-05-19 07:29:25 -06:00
|
|
|
],
|
2023-12-07 00:47:40 -07:00
|
|
|
"target": reactTag,
|
|
|
|
]
|
|
|
|
)
|
2022-05-19 07:29:25 -06:00
|
|
|
}
|
2022-11-09 09:26:12 -07:00
|
|
|
|
2023-12-07 00:47:40 -07:00
|
|
|
@objc
|
|
|
|
func handlePlaybackStalled(notification _: NSNotification!) {
|
2022-05-19 07:29:25 -06:00
|
|
|
onPlaybackStalled?(["target": reactTag as Any])
|
|
|
|
_playbackStalled = true
|
|
|
|
}
|
2022-11-09 09:26:12 -07:00
|
|
|
|
2023-12-07 00:47:40 -07:00
|
|
|
@objc
|
|
|
|
func handlePlayerItemDidReachEnd(notification: NSNotification!) {
|
2024-06-03 04:13:52 -06:00
|
|
|
sendProgressUpdate(didEnd: true)
|
2022-05-19 07:29:25 -06:00
|
|
|
onVideoEnd?(["target": reactTag as Any])
|
2023-12-07 00:47:40 -07:00
|
|
|
#if USE_GOOGLE_IMA
|
|
|
|
if notification.object as? AVPlayerItem == _player?.currentItem {
|
|
|
|
_imaAdsManager.getAdsLoader()?.contentComplete()
|
|
|
|
}
|
|
|
|
#endif
|
2022-05-19 07:29:25 -06:00
|
|
|
if _repeat {
|
2023-12-07 00:47:40 -07:00
|
|
|
let item: AVPlayerItem! = notification.object as? AVPlayerItem
|
2024-02-08 06:59:33 -07:00
|
|
|
|
2024-03-10 14:12:47 -06:00
|
|
|
item.seek(
|
|
|
|
to: _source?.cropStart != nil ? CMTime(value: _source!.cropStart!, timescale: 1000) : CMTime.zero,
|
|
|
|
toleranceBefore: CMTime.zero,
|
|
|
|
toleranceAfter: CMTime.zero,
|
|
|
|
completionHandler: { [weak self] _ in
|
|
|
|
guard let self else { return }
|
|
|
|
self.applyModifiers()
|
|
|
|
}
|
|
|
|
)
|
2022-05-19 07:29:25 -06:00
|
|
|
} else {
|
|
|
|
_playerObserver.removePlayerTimeObserver()
|
|
|
|
}
|
|
|
|
}
|
2023-12-02 05:52:01 -07:00
|
|
|
|
2023-12-07 00:47:40 -07:00
|
|
|
@objc
|
|
|
|
func handleAVPlayerAccess(notification: NSNotification!) {
|
2024-05-07 03:06:12 -06:00
|
|
|
guard onVideoBandwidthUpdate != nil else { return }
|
2024-05-08 08:07:32 -06:00
|
|
|
|
2024-02-29 06:40:11 -07:00
|
|
|
guard let accessLog = (notification.object as? AVPlayerItem)?.accessLog() else {
|
|
|
|
return
|
|
|
|
}
|
2023-12-02 05:52:01 -07:00
|
|
|
|
2024-02-29 06:40:11 -07:00
|
|
|
guard let lastEvent = accessLog.events.last else { return }
|
2024-09-03 07:33:43 -06:00
|
|
|
onVideoBandwidthUpdate?(["bitrate": lastEvent.indicatedBitrate, "target": reactTag])
|
2023-11-08 14:06:29 -07:00
|
|
|
}
|
2024-02-01 00:56:00 -07:00
|
|
|
|
|
|
|
func handleTracksChange(playerItem _: AVPlayerItem, change _: NSKeyValueObservedChange<[AVPlayerItemTrack]>) {
|
2024-05-08 08:07:32 -06:00
|
|
|
if onTextTracks != nil {
|
|
|
|
Task {
|
|
|
|
let textTracks = await RCTVideoUtils.getTextTrackInfo(self._player)
|
2024-09-02 09:01:39 -06:00
|
|
|
self.onTextTracks?(["textTracks": extractJsonWithIndex(from: _textTracks) ?? textTracks.compactMap(\.json)])
|
2024-05-08 08:07:32 -06:00
|
|
|
}
|
|
|
|
}
|
2024-04-04 05:23:44 -06:00
|
|
|
|
2024-05-08 08:07:32 -06:00
|
|
|
if onAudioTracks != nil {
|
|
|
|
Task {
|
|
|
|
let audioTracks = await RCTVideoUtils.getAudioTrackInfo(self._player)
|
|
|
|
self.onAudioTracks?(["audioTracks": audioTracks])
|
|
|
|
}
|
2024-02-01 00:56:00 -07:00
|
|
|
}
|
|
|
|
}
|
2024-02-29 06:41:04 -07:00
|
|
|
|
|
|
|
func handleLegibleOutput(strings: [NSAttributedString]) {
|
2024-05-08 08:07:32 -06:00
|
|
|
guard onTextTrackDataChanged != nil else { return }
|
|
|
|
|
2024-02-29 06:41:04 -07:00
|
|
|
if let subtitles = strings.first {
|
|
|
|
self.onTextTrackDataChanged?(["subtitleTracks": subtitles.string])
|
|
|
|
}
|
|
|
|
}
|
2024-04-18 06:18:17 -06:00
|
|
|
|
2024-05-28 03:00:38 -06:00
|
|
|
@objc
|
|
|
|
func getCurrentPlaybackTime(_ resolve: @escaping RCTPromiseResolveBlock, _ reject: @escaping RCTPromiseRejectBlock) {
|
|
|
|
if let player = _playerItem {
|
|
|
|
let currentTime = RCTVideoUtils.getCurrentTime(playerItem: player)
|
|
|
|
resolve(currentTime)
|
|
|
|
} else {
|
|
|
|
reject("PLAYER_NOT_AVAILABLE", "Player is not initialized.", nil)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2024-05-07 09:25:09 -06:00
|
|
|
// Workaround for #3418 - https://github.com/TheWidlarzGroup/react-native-video/issues/3418#issuecomment-2043508862
|
2024-04-18 06:18:17 -06:00
|
|
|
@objc
|
|
|
|
func setOnClick(_: Any) {}
|
2022-05-19 07:29:25 -06:00
|
|
|
}
|