feat!(ios): remove native dependency promises (#3631)

This commit is contained in:
Krzysztof Moch
2024-04-04 13:23:44 +02:00
committed by GitHub
parent 2633f087d2
commit 10b100de44
15 changed files with 686 additions and 785 deletions

View File

@@ -4,7 +4,6 @@ import Foundation
#if USE_GOOGLE_IMA
import GoogleInteractiveMediaAds
#endif
import Promises
import React
// MARK: - RCTVideo
@@ -316,6 +315,111 @@ class RCTVideo: UIView, RCTVideoPlayerViewControllerDelegate, RCTPlayerObserverH
// MARK: - Player and source
func preparePlayerItem() async throws -> AVPlayerItem {
guard let source = self._source else {
DebugLog("The source not exist")
self.isSetSourceOngoing = false
self.applyNextSource()
throw NSError(domain: "", code: 0, userInfo: nil)
}
if let uri = source.uri, uri.starts(with: "ph://") {
let photoAsset = await RCTVideoUtils.preparePHAsset(uri: uri)
return await self.playerItemPrepareText(asset: photoAsset, assetOptions: nil, uri: source.uri ?? "")
}
guard let assetResult = RCTVideoUtils.prepareAsset(source: source),
let asset = assetResult.asset,
let assetOptions = assetResult.assetOptions else {
DebugLog("Could not find video URL in source '\(String(describing: self._source))'")
self.isSetSourceOngoing = false
self.applyNextSource()
throw NSError(domain: "", code: 0, userInfo: nil)
}
guard let assetResult = RCTVideoUtils.prepareAsset(source: source),
let asset = assetResult.asset,
let assetOptions = assetResult.assetOptions else {
DebugLog("Could not find video URL in source '\(String(describing: self._source))'")
self.isSetSourceOngoing = false
self.applyNextSource()
throw NSError(domain: "", code: 0, userInfo: nil)
}
if let startPosition = self._source?.startPosition {
self._startPosition = startPosition / 1000
}
#if USE_VIDEO_CACHING
if self._videoCache.shouldCache(source: source, textTracks: self._textTracks) {
return try await self._videoCache.playerItemForSourceUsingCache(uri: source.uri, assetOptions: assetOptions)
}
#endif
if self._drm != nil || self._localSourceEncryptionKeyScheme != nil {
self._resouceLoaderDelegate = RCTResourceLoaderDelegate(
asset: asset,
drm: self._drm,
localSourceEncryptionKeyScheme: self._localSourceEncryptionKeyScheme,
onVideoError: self.onVideoError,
onGetLicense: self.onGetLicense,
reactTag: self.reactTag
)
}
return await playerItemPrepareText(asset: asset, assetOptions: assetOptions, uri: source.uri ?? "")
}
func setupPlayer(playerItem: AVPlayerItem) async throws {
if !self.isSetSourceOngoing {
DebugLog("setSrc has been canceled last step")
return
}
self._player?.pause()
self._playerItem = playerItem
self._playerObserver.playerItem = self._playerItem
self.setPreferredForwardBufferDuration(self._preferredForwardBufferDuration)
self.setPlaybackRange(playerItem, withCropStart: self._source?.cropStart, withCropEnd: self._source?.cropEnd)
self.setFilter(self._filterName)
if let maxBitRate = self._maxBitRate {
self._playerItem?.preferredPeakBitRate = Double(maxBitRate)
}
self._player = self._player ?? AVPlayer()
self._player?.replaceCurrentItem(with: playerItem)
self._playerObserver.player = self._player
self.applyModifiers()
self._player?.actionAtItemEnd = .none
if #available(iOS 10.0, *) {
self.setAutomaticallyWaitsToMinimizeStalling(self._automaticallyWaitsToMinimizeStalling)
}
#if USE_GOOGLE_IMA
if self._adTagUrl != nil {
// Set up your content playhead and contentComplete callback.
self._contentPlayhead = IMAAVPlayerContentPlayhead(avPlayer: self._player!)
self._imaAdsManager.setUpAdsLoader()
}
#endif
// Perform on next run loop, otherwise onVideoLoadStart is nil
self.onVideoLoadStart?([
"src": [
"uri": self._source?.uri ?? NSNull(),
"type": self._source?.type ?? NSNull(),
"isNetwork": NSNumber(value: self._source?.isNetwork ?? false),
],
"drm": self._drm?.json ?? NSNull(),
"target": self.reactTag,
])
self.isSetSourceOngoing = false
self.applyNextSource()
}
@objc
func setSrc(_ source: NSDictionary!) {
if self.isSetSourceOngoing || self.nextSource != nil {
@@ -326,7 +430,7 @@ class RCTVideo: UIView, RCTVideoPlayerViewControllerDelegate, RCTPlayerObserverH
}
self.isSetSourceOngoing = true
let dispatchClosure = {
let initializeSource = {
self._source = VideoSource(source)
if self._source?.uri == nil || self._source?.uri == "" {
self._player?.replaceCurrentItem(with: nil)
@@ -341,111 +445,28 @@ class RCTVideo: UIView, RCTVideoPlayerViewControllerDelegate, RCTPlayerObserverH
self._playerObserver.playerItem = nil
// perform on next run loop, otherwise other passed react-props may not be set
RCTVideoUtils.delay()
.then { [weak self] in
RCTVideoUtils.delay { [weak self] in
do {
guard let self else { throw NSError(domain: "", code: 0, userInfo: nil) }
guard let source = self._source else {
DebugLog("The source not exist")
self.isSetSourceOngoing = false
self.applyNextSource()
throw NSError(domain: "", code: 0, userInfo: nil)
}
if let uri = source.uri, uri.starts(with: "ph://") {
return Promise {
RCTVideoUtils.preparePHAsset(uri: uri).then { asset in
return self.playerItemPrepareText(asset: asset, assetOptions: nil, uri: source.uri ?? "")
}
}
}
guard let assetResult = RCTVideoUtils.prepareAsset(source: source),
let asset = assetResult.asset,
let assetOptions = assetResult.assetOptions else {
DebugLog("Could not find video URL in source '\(String(describing: self._source))'")
self.isSetSourceOngoing = false
self.applyNextSource()
throw NSError(domain: "", code: 0, userInfo: nil)
}
if let startPosition = self._source?.startPosition {
self._startPosition = startPosition / 1000
}
#if USE_VIDEO_CACHING
if self._videoCache.shouldCache(source: source, textTracks: self._textTracks) {
return self._videoCache.playerItemForSourceUsingCache(uri: source.uri, assetOptions: assetOptions)
}
#endif
if self._drm != nil || self._localSourceEncryptionKeyScheme != nil {
self._resouceLoaderDelegate = RCTResourceLoaderDelegate(
asset: asset,
drm: self._drm,
localSourceEncryptionKeyScheme: self._localSourceEncryptionKeyScheme,
onVideoError: self.onVideoError,
onGetLicense: self.onGetLicense,
reactTag: self.reactTag
)
}
return self.playerItemPrepareText(asset: asset, assetOptions: assetOptions, uri: source.uri ?? "")
}.then { [weak self] (playerItem: AVPlayerItem!) in
guard let self else { throw NSError(domain: "", code: 0, userInfo: nil) }
if !self.isSetSourceOngoing {
DebugLog("setSrc has been canceled last step")
return
}
self._player?.pause()
self._playerItem = playerItem
self._playerObserver.playerItem = self._playerItem
self.setPreferredForwardBufferDuration(self._preferredForwardBufferDuration)
self.setPlaybackRange(playerItem, withCropStart: self._source?.cropStart, withCropEnd: self._source?.cropEnd)
self.setFilter(self._filterName)
if let maxBitRate = self._maxBitRate {
self._playerItem?.preferredPeakBitRate = Double(maxBitRate)
}
self._player = self._player ?? AVPlayer()
self._player?.replaceCurrentItem(with: playerItem)
self._playerObserver.player = self._player
self.applyModifiers()
self._player?.actionAtItemEnd = .none
if #available(iOS 10.0, *) {
self.setAutomaticallyWaitsToMinimizeStalling(self._automaticallyWaitsToMinimizeStalling)
}
#if USE_GOOGLE_IMA
if self._adTagUrl != nil {
// Set up your content playhead and contentComplete callback.
self._contentPlayhead = IMAAVPlayerContentPlayhead(avPlayer: self._player!)
self._imaAdsManager.setUpAdsLoader()
}
#endif
// Perform on next run loop, otherwise onVideoLoadStart is nil
self.onVideoLoadStart?([
"src": [
"uri": self._source?.uri ?? NSNull(),
"type": self._source?.type ?? NSNull(),
"isNetwork": NSNumber(value: self._source?.isNetwork ?? false),
],
"drm": self._drm?.json ?? NSNull(),
"target": self.reactTag,
])
self.isSetSourceOngoing = false
self.applyNextSource()
}.catch { error in
let playerItem = try await self.preparePlayerItem()
try await setupPlayer(playerItem: playerItem)
} catch {
DebugLog("An error occurred: \(error.localizedDescription)")
self.onVideoError?(["error": error.localizedDescription])
self.isSetSourceOngoing = false
self.applyNextSource()
if let self {
self.onVideoError?(["error": error.localizedDescription])
self.isSetSourceOngoing = false
self.applyNextSource()
}
}
}
self._videoLoadStarted = true
self.applyNextSource()
}
DispatchQueue.global(qos: .default).async(execute: dispatchClosure)
DispatchQueue.global(qos: .default).async(execute: initializeSource)
}
@objc
@@ -458,32 +479,26 @@ class RCTVideo: UIView, RCTVideoPlayerViewControllerDelegate, RCTPlayerObserverH
_localSourceEncryptionKeyScheme = keyScheme
}
func playerItemPrepareText(asset: AVAsset!, assetOptions: NSDictionary?, uri: String) -> Promise<AVPlayerItem> {
return Promise { [weak self] fulfill, _ in
guard let self else { return }
if (self._textTracks == nil) || self._textTracks?.isEmpty == true || (uri.hasSuffix(".m3u8")) {
fulfill(self.playerItemPropegateMetadata(AVPlayerItem(asset: asset)))
return
}
// AVPlayer can't airplay AVMutableCompositions
self._allowsExternalPlayback = false
RCTVideoUtils.generateMixComposition(asset).then { mixComposition in
RCTVideoUtils.getValidTextTracks(
asset: asset,
assetOptions: assetOptions,
mixComposition: mixComposition,
textTracks: self._textTracks
).then { [self] validTextTracks in
if validTextTracks.count != self._textTracks?.count {
self.setTextTracks(validTextTracks)
}
fulfill(self.playerItemPropegateMetadata(AVPlayerItem(asset: mixComposition)))
}
}
func playerItemPrepareText(asset: AVAsset!, assetOptions: NSDictionary?, uri: String) async -> AVPlayerItem {
if (self._textTracks == nil) || self._textTracks?.isEmpty == true || (uri.hasSuffix(".m3u8")) {
return self.playerItemPropegateMetadata(AVPlayerItem(asset: asset))
}
// 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
)
if validTextTracks.count != self._textTracks?.count {
self.setTextTracks(validTextTracks)
}
return self.playerItemPropegateMetadata(AVPlayerItem(asset: mixComposition))
}
func playerItemPropegateMetadata(_ playerItem: AVPlayerItem!) -> AVPlayerItem {
@@ -658,8 +673,7 @@ class RCTVideo: UIView, RCTVideoPlayerViewControllerDelegate, RCTPlayerObserverH
paused: wasPaused,
seekTime: seekTime.floatValue,
seekTolerance: seekTolerance.floatValue
)
.then { [weak self] (_: Bool) in
) { [weak self] (_: Bool) in
guard let self else { return }
self._playerObserver.addTimeObserverIfNotSet()
@@ -669,7 +683,7 @@ class RCTVideo: UIView, RCTVideoPlayerViewControllerDelegate, RCTPlayerObserverH
self.onVideoSeek?(["currentTime": NSNumber(value: Float(CMTimeGetSeconds(item.currentTime()))),
"seekTime": seekTime,
"target": self.reactTag])
}.catch { _ in }
}
_pendingSeek = false
}
@@ -801,8 +815,10 @@ class RCTVideo: UIView, RCTVideoPlayerViewControllerDelegate, RCTPlayerObserverH
func setSelectedAudioTrack(_ selectedAudioTrack: SelectedTrackCriteria?) {
_selectedAudioTrackCriteria = selectedAudioTrack
RCTPlayerOperations.setMediaSelectionTrackForCharacteristic(player: _player, characteristic: AVMediaCharacteristic.audible,
criteria: _selectedAudioTrackCriteria)
Task {
await RCTPlayerOperations.setMediaSelectionTrackForCharacteristic(player: _player, characteristic: AVMediaCharacteristic.audible,
criteria: _selectedAudioTrackCriteria)
}
}
@objc
@@ -815,8 +831,10 @@ class RCTVideo: UIView, RCTVideoPlayerViewControllerDelegate, RCTPlayerObserverH
if _textTracks != nil { // sideloaded text tracks
RCTPlayerOperations.setSideloadedText(player: _player, textTracks: _textTracks!, criteria: _selectedTextTrackCriteria)
} else { // text tracks included in the HLS playlist§
RCTPlayerOperations.setMediaSelectionTrackForCharacteristic(player: _player, characteristic: AVMediaCharacteristic.legible,
criteria: _selectedTextTrackCriteria)
Task {
await RCTPlayerOperations.setMediaSelectionTrackForCharacteristic(player: _player, characteristic: AVMediaCharacteristic.legible,
criteria: _selectedTextTrackCriteria)
}
}
}
@@ -1035,8 +1053,9 @@ class RCTVideo: UIView, RCTVideoPlayerViewControllerDelegate, RCTPlayerObserverH
}
let filter: CIFilter! = CIFilter(name: filterName)
RCTVideoUtils.generateVideoComposition(asset: _playerItem!.asset, filter: filter).then { [weak self] composition in
self?._playerItem?.videoComposition = composition
Task {
let composition = await RCTVideoUtils.generateVideoComposition(asset: _playerItem!.asset, filter: filter)
self._playerItem?.videoComposition = composition
}
}
@@ -1213,9 +1232,8 @@ class RCTVideo: UIView, RCTVideoPlayerViewControllerDelegate, RCTPlayerObserverH
var height: Float?
var orientation = "undefined"
RCTVideoAssetsUtils.getTracks(asset: _playerItem.asset, withMediaType: .video).then { [weak self] tracks in
guard let self else { return }
Task {
let tracks = await RCTVideoAssetsUtils.getTracks(asset: _playerItem.asset, withMediaType: .video)
if let videoTrack = tracks?.first {
width = Float(videoTrack.naturalSize.width)
height = Float(videoTrack.naturalSize.height)
@@ -1251,25 +1269,26 @@ class RCTVideo: UIView, RCTVideoPlayerViewControllerDelegate, RCTPlayerObserverH
}
if self._videoLoadStarted {
all(RCTVideoUtils.getAudioTrackInfo(self._player), RCTVideoUtils.getTextTrackInfo(self._player)).then { audioTracks, textTracks in
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": [
"width": width != nil ? NSNumber(value: width!) : "undefinded",
"height": width != nil ? NSNumber(value: height!) : "undefinded",
"orientation": orientation,
],
"audioTracks": audioTracks,
"textTracks": self._textTracks?.compactMap { $0.json } ?? textTracks.map(\.json),
"target": self.reactTag as Any])
}
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": [
"width": width != nil ? NSNumber(value: width!) : "undefinded",
"height": width != nil ? NSNumber(value: height!) : "undefinded",
"orientation": orientation,
],
"audioTracks": audioTracks,
"textTracks": self._textTracks?.compactMap { $0.json } ?? textTracks.map(\.json),
"target": self.reactTag as Any])
}
self._videoLoadStarted = false
self._playerObserver.attachPlayerEventListeners()
self.applyModifiers()
@@ -1432,7 +1451,10 @@ class RCTVideo: UIView, RCTVideoPlayerViewControllerDelegate, RCTPlayerObserverH
}
func handleTracksChange(playerItem _: AVPlayerItem, change _: NSKeyValueObservedChange<[AVPlayerItemTrack]>) {
all(RCTVideoUtils.getAudioTrackInfo(self._player), RCTVideoUtils.getTextTrackInfo(self._player)).then { audioTracks, textTracks in
Task {
let audioTracks = await RCTVideoUtils.getAudioTrackInfo(self._player)
let textTracks = await RCTVideoUtils.getTextTrackInfo(self._player)
self.onTextTracks?(["textTracks": textTracks])
self.onAudioTracks?(["audioTracks": audioTracks])
}