feat: add visionOS support (#3425)

* feat: add visionOS to target platforms

* disable unsupported API

* add temporary `promises` patches

* fix(visionOS): update promises patches

* apply code review suggestions

* format code
This commit is contained in:
Krzysztof Moch 2024-01-15 08:04:29 +01:00 committed by GitHub
parent 8f1bdb7c36
commit cf3ebb7f15
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
7 changed files with 108 additions and 42 deletions

View File

@ -118,7 +118,9 @@ class RCTPlayerObserver: NSObject, AVPlayerItemMetadataOutputPushDelegate {
_playerRateChangeObserver = player.observe(\.rate, options: [.old], changeHandler: _handlers.handlePlaybackRateChange) _playerRateChangeObserver = player.observe(\.rate, options: [.old], changeHandler: _handlers.handlePlaybackRateChange)
_playerVolumeChangeObserver = player.observe(\.volume, options: [.old], changeHandler: _handlers.handleVolumeChange) _playerVolumeChangeObserver = player.observe(\.volume, options: [.old], changeHandler: _handlers.handleVolumeChange)
_playerExternalPlaybackActiveObserver = player.observe(\.isExternalPlaybackActive, changeHandler: _handlers.handleExternalPlaybackActiveChange) #if !os(visionOS)
_playerExternalPlaybackActiveObserver = player.observe(\.isExternalPlaybackActive, changeHandler: _handlers.handleExternalPlaybackActiveChange)
#endif
} }
func removePlayerObservers() { func removePlayerObservers() {
@ -151,11 +153,13 @@ class RCTPlayerObserver: NSObject, AVPlayerItemMetadataOutputPushDelegate {
func addPlayerViewControllerObservers() { func addPlayerViewControllerObservers() {
guard let playerViewController, let _handlers else { return } guard let playerViewController, let _handlers else { return }
_playerViewControllerReadyForDisplayObserver = playerViewController.observe( #if !os(visionOS)
\.isReadyForDisplay, _playerViewControllerReadyForDisplayObserver = playerViewController.observe(
options: [.new], \.isReadyForDisplay,
changeHandler: _handlers.handleReadyForDisplay options: [.new],
) changeHandler: _handlers.handleReadyForDisplay
)
#endif
_playerViewControllerOverlayFrameObserver = playerViewController.contentOverlayView?.observe( _playerViewControllerOverlayFrameObserver = playerViewController.contentOverlayView?.observe(
\.frame, \.frame,

View File

@ -78,24 +78,21 @@ enum RCTVideoDRM {
contentIdData: Data contentIdData: Data
) -> Promise<Data> { ) -> Promise<Data> {
return Promise<Data>(on: .global()) { fulfill, reject in return Promise<Data>(on: .global()) { fulfill, reject in
var spcError: NSError! #if os(visionOS)
var spcData: Data? // TODO: DRM is not supported yet on visionOS. See #3467
do { reject(NSError(domain: "DRM is not supported yet on visionOS", code: 0, userInfo: nil))
spcData = try loadingRequest.streamingContentKeyRequestData(forApp: certificateData, contentIdentifier: contentIdData as Data, options: nil) #else
} catch _ { guard let spcData = try? loadingRequest.streamingContentKeyRequestData(
print("SPC error") forApp: certificateData,
} contentIdentifier: contentIdData as Data,
options: nil
) else {
reject(RCTVideoErrorHandler.noSPC)
return
}
if spcError != nil { fulfill(spcData)
reject(spcError) #endif
}
guard let spcData else {
reject(RCTVideoErrorHandler.noSPC)
return
}
fulfill(spcData)
} }
} }

View File

@ -28,9 +28,11 @@ enum RCTVideoAssetsUtils {
asset.loadTracks(withMediaType: withMediaType, completionHandler: handler) asset.loadTracks(withMediaType: withMediaType, completionHandler: handler)
} }
} else { } else {
return Promise { fulfill, _ in #if !os(visionOS)
fulfill(asset.tracks(withMediaType: withMediaType)) return Promise { fulfill, _ in
} fulfill(asset.tracks(withMediaType: withMediaType))
}
#endif
} }
} }
} }

View File

@ -508,7 +508,9 @@ class RCTVideo: UIView, RCTVideoPlayerViewControllerDelegate, RCTPlayerObserverH
@objc @objc
func setAllowsExternalPlayback(_ allowsExternalPlayback: Bool) { func setAllowsExternalPlayback(_ allowsExternalPlayback: Bool) {
_allowsExternalPlayback = allowsExternalPlayback _allowsExternalPlayback = allowsExternalPlayback
_player?.allowsExternalPlayback = _allowsExternalPlayback #if !os(visionOS)
_player?.allowsExternalPlayback = _allowsExternalPlayback
#endif
} }
@objc @objc
@ -642,7 +644,7 @@ class RCTVideo: UIView, RCTVideoPlayerViewControllerDelegate, RCTPlayerObserverH
RCTPlayerOperations.configureAudio(ignoreSilentSwitch: _ignoreSilentSwitch, mixWithOthers: _mixWithOthers, audioOutput: _audioOutput) RCTPlayerOperations.configureAudio(ignoreSilentSwitch: _ignoreSilentSwitch, mixWithOthers: _mixWithOthers, audioOutput: _audioOutput)
do { do {
if audioOutput == "speaker" { if audioOutput == "speaker" {
#if os(iOS) #if os(iOS) || os(visionOS)
try AVAudioSession.sharedInstance().overrideOutputAudioPort(AVAudioSession.PortOverride.speaker) try AVAudioSession.sharedInstance().overrideOutputAudioPort(AVAudioSession.PortOverride.speaker)
#endif #endif
} else if audioOutput == "earpiece" { } else if audioOutput == "earpiece" {
@ -713,7 +715,9 @@ class RCTVideo: UIView, RCTVideoPlayerViewControllerDelegate, RCTPlayerObserverH
} }
if #available(iOS 12.0, tvOS 12.0, *) { if #available(iOS 12.0, tvOS 12.0, *) {
_player?.preventsDisplaySleepDuringVideoPlayback = _preventsDisplaySleepDuringVideoPlayback #if !os(visionOS)
_player?.preventsDisplaySleepDuringVideoPlayback = _preventsDisplaySleepDuringVideoPlayback
#endif
} else { } else {
// Fallback on earlier versions // Fallback on earlier versions
} }
@ -972,12 +976,8 @@ class RCTVideo: UIView, RCTVideoPlayerViewControllerDelegate, RCTPlayerObserverH
} }
let filter: CIFilter! = CIFilter(name: filterName) let filter: CIFilter! = CIFilter(name: filterName)
if #available(iOS 9.0, *), let _playerItem { RCTVideoUtils.generateVideoComposition(asset: _playerItem!.asset, filter: filter).then { [weak self] composition in
RCTVideoUtils.generateVideoComposition(asset: _playerItem.asset, filter: filter).then { [weak self] composition in self?._playerItem?.videoComposition = composition
self?._playerItem?.videoComposition = composition
}
} else {
// Fallback on earlier versions
} }
} }
@ -1280,9 +1280,11 @@ class RCTVideo: UIView, RCTVideoPlayerViewControllerDelegate, RCTPlayerObserverH
} }
func handleExternalPlaybackActiveChange(player _: AVPlayer, change _: NSKeyValueObservedChange<Bool>) { func handleExternalPlaybackActiveChange(player _: AVPlayer, change _: NSKeyValueObservedChange<Bool>) {
guard let _player else { return } #if !os(visionOS)
onVideoExternalPlaybackChange?(["isExternalPlaybackActive": NSNumber(value: _player.isExternalPlaybackActive), guard let _player else { return }
"target": reactTag as Any]) onVideoExternalPlaybackChange?(["isExternalPlaybackActive": NSNumber(value: _player.isExternalPlaybackActive),
"target": reactTag as Any])
#endif
} }
func handleViewControllerOverlayViewFrameChange(overlayView _: UIView, change: NSKeyValueObservedChange<CGRect>) { func handleViewControllerOverlayViewFrameChange(overlayView _: UIView, change: NSKeyValueObservedChange<CGRect>) {

View File

@ -0,0 +1,41 @@
Pod::Spec.new do |s|
s.name = 'PromisesObjC'
s.version = '2.3.1.1'
s.authors = 'Google Inc.'
s.license = { :type => 'Apache-2.0', :file => 'LICENSE' }
s.homepage = 'https://github.com/google/promises'
s.source = { :git => 'https://github.com/google/promises.git', :tag => '2.3.1' }
s.summary = 'Synchronization construct for Objective-C'
s.description = <<-DESC
Promises is a modern framework that provides a synchronization construct for
Objective-C to facilitate writing asynchronous code.
DESC
s.platforms = { :ios => '9.0', :osx => '10.11', :tvos => '9.0', :watchos => '2.0', :visionos => '1.0' }
s.module_name = 'FBLPromises'
s.prefix_header_file = false
s.header_dir = "./"
s.public_header_files = "Sources/#{s.module_name}/include/**/*.h"
s.private_header_files = "Sources/#{s.module_name}/include/FBLPromisePrivate.h"
s.source_files = "Sources/#{s.module_name}/**/*.{h,m}"
s.pod_target_xcconfig = {
'DEFINES_MODULE' => 'YES'
}
s.test_spec 'Tests' do |ts|
# Note: Omits watchOS as a workaround since XCTest is not available to watchOS for now.
# Reference: https://github.com/CocoaPods/CocoaPods/issues/8283, https://github.com/CocoaPods/CocoaPods/issues/4185.
ts.platforms = {:ios => nil, :osx => nil, :tvos => nil}
ts.source_files = "Tests/#{s.module_name}Tests/*.m",
"Sources/#{s.module_name}TestHelpers/include/#{s.module_name}TestHelpers.h"
end
s.test_spec 'PerformanceTests' do |ts|
# Note: Omits watchOS as a workaround since XCTest is not available to watchOS for now.
# Reference: https://github.com/CocoaPods/CocoaPods/issues/8283, https://github.com/CocoaPods/CocoaPods/issues/4185.
ts.platforms = {:ios => nil, :osx => nil, :tvos => nil}
ts.source_files = "Tests/#{s.module_name}PerformanceTests/*.m",
"Sources/#{s.module_name}TestHelpers/include/#{s.module_name}TestHelpers.h"
end
end

View File

@ -0,0 +1,21 @@
Pod::Spec.new do |s|
s.name = 'PromisesSwift'
s.version = '2.3.1.1'
s.authors = 'Google Inc.'
s.license = { :type => 'Apache-2.0', :file => 'LICENSE' }
s.homepage = 'https://github.com/google/promises'
s.source = { :git => 'https://github.com/google/promises.git', :tag => '2.3.1' }
s.summary = 'Synchronization construct for Swift'
s.description = <<-DESC
Promises is a modern framework that provides a synchronization construct for
Swift to facilitate writing asynchronous code.
DESC
s.platforms = { :ios => '9.0', :osx => '10.11', :tvos => '9.0', :watchos => '2.0', :visionos => '1.0' }
s.swift_versions = ['5.0', '5.2']
s.module_name = 'Promises'
s.source_files = "Sources/#{s.module_name}/*.{swift}"
s.dependency 'PromisesObjC', "#{s.version}"
end

View File

@ -9,11 +9,10 @@ Pod::Spec.new do |s|
s.description = package['description'] s.description = package['description']
s.license = package['license'] s.license = package['license']
s.author = package['author'] s.author = package['author']
s.homepage = 'https://github.com/react-native-video/react-native-video'
s.source = { :git => "https://github.com/react-native-video/react-native-video.git", :tag => "v#{s.version}" }
s.ios.deployment_target = "9.0" s.homepage = 'https://github.com/react-native-video/react-native-video'
s.tvos.deployment_target = "10.0" s.source = { :git => "https://github.com/react-native-video/react-native-video.git", :tag => "v#{s.version}" }
s.platforms = { :ios => "9.0", :tvos => "10.0", :visionos => "1.0" }
s.subspec "Video" do |ss| s.subspec "Video" do |ss|
ss.source_files = "ios/Video/**/*.{h,m,swift}" ss.source_files = "ios/Video/**/*.{h,m,swift}"