From 340f3b5d8b788cccb20a7e789192c2b44d40e27f Mon Sep 17 00:00:00 2001 From: ThibaultBee <37510686+ThibaultBee@users.noreply.github.com> Date: Tue, 2 Jan 2024 12:20:55 +0100 Subject: [PATCH] feat(ios): add support for new architecture --- example/ios/_xcode.env | 11 -- ios/RNLiveStreamView.h | 20 +++ ios/RNLiveStreamView.mm | 141 ++++++++++++++++++ ios/RNLiveStreamView.swift | 67 --------- ios/RNLiveStreamViewImpl.swift | 50 +++---- ...ewManager.m => RNLiveStreamViewManager.mm} | 13 -- ios/RNLivestream.xcodeproj/project.pbxproj | 25 ++-- react-native-livestream.podspec | 2 +- 8 files changed, 196 insertions(+), 133 deletions(-) delete mode 100644 example/ios/_xcode.env create mode 100644 ios/RNLiveStreamView.h create mode 100644 ios/RNLiveStreamView.mm delete mode 100644 ios/RNLiveStreamView.swift rename ios/{RNLiveStreamViewManager.m => RNLiveStreamViewManager.mm} (71%) diff --git a/example/ios/_xcode.env b/example/ios/_xcode.env deleted file mode 100644 index 3d5782c..0000000 --- a/example/ios/_xcode.env +++ /dev/null @@ -1,11 +0,0 @@ -# This `.xcode.env` file is versioned and is used to source the environment -# used when running script phases inside Xcode. -# To customize your local environment, you can create an `.xcode.env.local` -# file that is not versioned. - -# NODE_BINARY variable contains the PATH to the node executable. -# -# Customize the NODE_BINARY variable here. -# For example, to use nvm with brew, add the following line -# . "$(brew --prefix nvm)/nvm.sh" --no-use -export NODE_BINARY=$(command -v node) diff --git a/ios/RNLiveStreamView.h b/ios/RNLiveStreamView.h new file mode 100644 index 0000000..2136c53 --- /dev/null +++ b/ios/RNLiveStreamView.h @@ -0,0 +1,20 @@ +// This guard prevent this file to be compiled in the old architecture. +#ifdef RCT_NEW_ARCH_ENABLED +#import +#import +#import +#import +#import + +#ifndef RNLiveStreamView_h +#define RNLiveStreamView_h + +NS_ASSUME_NONNULL_BEGIN + +@interface RNLiveStreamView : RCTViewComponentView +@end + +NS_ASSUME_NONNULL_END + +#endif /* RNLiveStreamView_h */ +#endif /* RCT_NEW_ARCH_ENABLED */ diff --git a/ios/RNLiveStreamView.mm b/ios/RNLiveStreamView.mm new file mode 100644 index 0000000..7bca325 --- /dev/null +++ b/ios/RNLiveStreamView.mm @@ -0,0 +1,141 @@ +// This guard prevent the code from being compiled in the old architecture +#ifdef RCT_NEW_ARCH_ENABLED +#import "RNLiveStreamView.h" + +#import +#import +#import +#import + +#import "RCTFabricComponentsPlugins.h" + +// MARK: Swift classes in ObjC++ +#if __has_include("react-native-livestream/react_native_livestream-Swift.h") +#import "react-native-livestream/react_native_livestream-Swift.h" +#else +#import "react_native_livestream-Swift.h" +#endif + +using namespace facebook::react; + +@class RNLiveStreamViewImpl; + +@interface RNLiveStreamView () + +@end + +// MARK: Implementation + +@implementation RNLiveStreamView { + RNLiveStreamViewImpl * _view; +} + +// MARK: Initializers +- (instancetype)initWithFrame:(CGRect)frame +{ + self = [super initWithFrame:frame]; + if (self) { + static const auto defaultProps = std::make_shared(); + _props = defaultProps; + + _view = [[RNLiveStreamViewImpl alloc] init]; + // TODO events + /*_view.onConnectionSuccess = [self]() { + if (_eventEmitter) { + auto liveStreamEventEmitter = std::dynamic_pointer_cast(_eventEmitter); + ApiVideoLiveStreamViewEventEmitter::OnConnectionSuccess data = {}; + liveStreamEventEmitter->OnConnectionSuccess(data); + } + }; + _view.onConnectionFailed = [self](NSString* code) { + if (_eventEmitter) { + auto liveStreamEventEmitter = std::dynamic_pointer_cast(_eventEmitter); + ApiVideoLiveStreamViewEventEmitter::OnConnectionFailed data = { + .code = std::string([code UTF8String]) + }; + liveStreamEventEmitter->OnConnectionFailed(data); + } + }; + _view.onDisconnect = [self]() { + if (_eventEmitter) { + auto liveStreamEventEmitter = std::dynamic_pointer_cast(_eventEmitter); + ApiVideoLiveStreamViewEventEmitter::OnDisconnect data = {}; + liveStreamEventEmitter->OnDisconnect(data); + } + };*/ + + self.contentView = _view; + } + return self; +} + +// MARK: Props +- (void)updateProps:(Props::Shared const &)props oldProps:(Props::Shared const &)oldProps +{ + const auto &oldViewProps = *std::static_pointer_cast(_props); + const auto &newViewProps = *std::static_pointer_cast(props); + + RNLiveStreamViewImpl *view = (RNLiveStreamViewImpl *)self.contentView; + if (oldViewProps.camera != newViewProps.camera) { + NSString *camera = RCTNSStringFromStringNilIfEmpty(toString(newViewProps.camera)); + [view setCamera:camera]; + } + + if ((oldViewProps.video.bitrate != newViewProps.video.bitrate) || (oldViewProps.video.fps != newViewProps.video.fps) || (oldViewProps.video.resolution != newViewProps.video.resolution) || (oldViewProps.video.gopDuration != newViewProps.video.gopDuration)) { + NSString *resolution = RCTNSStringFromStringNilIfEmpty(toString(newViewProps.video.resolution)); + NSDictionary *config = @{ @"bitrate" : @(newViewProps.video.bitrate), @"resolution" : resolution, @"fps" : @(newViewProps.video.fps), @"gopDuration" : @(newViewProps.video.gopDuration)}; + [view setVideo:config]; + } + + if (oldViewProps.isMuted != newViewProps.isMuted) { + [view setIsMuted:newViewProps.isMuted]; + } + + if ((oldViewProps.audio.bitrate != newViewProps.audio.bitrate) || (oldViewProps.audio.sampleRate != newViewProps.audio.sampleRate) || (oldViewProps.audio.isStereo != newViewProps.audio.isStereo)) { + NSString *sampleRate = RCTNSStringFromStringNilIfEmpty(toString(newViewProps.audio.sampleRate)); + NSDictionary *config = @{ @"bitrate" : @(newViewProps.audio.bitrate), @"sampleRate" : @([sampleRate intValue]), @"isStereo" : @(newViewProps.audio.isStereo)}; + [view setAudio:config]; + } + + if (oldViewProps.zoomRatio != newViewProps.zoomRatio) { + [view setZoomRatio:newViewProps.zoomRatio]; + } + + if (oldViewProps.enablePinchedZoom != newViewProps.enablePinchedZoom) { + [view setEnablePinchedZoom:newViewProps.enablePinchedZoom]; + } + + [super updateProps:props oldProps:oldProps]; +} + +// MARK: RCTComponentViewProtocol +- (void)startStreaming:(NSString *)streamKey url:(NSString *)url +{ + NSError *error = nil; + [_view startStreaming:streamKey url:url error:&error]; + +} + +- (void)stopStreaming +{ + [_view stopStreaming]; +} + +- (void)setZoomRatioCommand:(float)zoomRatio +{ + [_view setZoomRatioWithZoomRatio:zoomRatio]; +} + ++ (ComponentDescriptorProvider)componentDescriptorProvider +{ + return concreteComponentDescriptorProvider(); +} + +@end + +Class ApiVideoLiveStreamViewCls(void) +{ + return RNLiveStreamView.class; +} + +#endif diff --git a/ios/RNLiveStreamView.swift b/ios/RNLiveStreamView.swift deleted file mode 100644 index cbc06d6..0000000 --- a/ios/RNLiveStreamView.swift +++ /dev/null @@ -1,67 +0,0 @@ -// This guard prevent the code from being compiled in the old architecture -#if RCT_NEW_ARCH_ENABLED - - import Foundation - - @objc class RNLiveStreamView: RCTViewComponentView { - init(frame: CGRect) { - super.init(frame: frame) - view = RNLiveStreamViewImpl() - } - - static func componentDescriptorProvider() -> Any! {} - - static func supplementalComponentDescriptorProviders() -> Any! {} - - func mountChildComponentView(_: UIView & RCTComponentViewProtocol, index _: Int) {} - - func unmountChildComponentView(_: UIView & RCTComponentViewProtocol, index _: Int) {} - - @objc func updateProps(_: Int32, oldProps _: Int32) {} - - func updateState(_: Int32, oldState _: Int32) {} - - func updateEventEmitter(_ eventEmitter: Int32) { - super.updateEventEmitter(eventEmitter) - } - - func updateLayoutMetrics(_: Int32, oldLayoutMetrics _: Int32) {} - - func handleCommand(_: String, args _: [Any]) {} - - func finalizeUpdates(_: Any!) {} - - func prepareForRecycle() {} - - func props() -> Any! {} - - func isJSResponder() -> Bool {} - - func setIsJSResponder(_: Bool) {} - - func setPropKeysManagedByAnimated_DO_NOT_USE_THIS_IS_BROKEN(_: Set?) {} - - func propKeysManagedByAnimated_DO_NOT_USE_THIS_IS_BROKEN() -> Set? {} - - @objc - func startStreaming(_ streamKey: String, url: String) { - do { - try view.startStreaming(streamKey, url: url) - } catch { - // TODO: return error - print(error) - } - } - - @objc - func stopStreaming() { - view.stopStreaming() - } - - @objc - func setZoomRatioCommand(_ zoomRatio: Float) { - view.setZoomRatio(zoomRatio: CGFloat(zoomRatio)) - } - } - -#endif diff --git a/ios/RNLiveStreamViewImpl.swift b/ios/RNLiveStreamViewImpl.swift index 5818694..7374e98 100644 --- a/ios/RNLiveStreamViewImpl.swift +++ b/ios/RNLiveStreamViewImpl.swift @@ -51,7 +51,8 @@ extension AVCaptureDevice.Position { } } -class RNLiveStreamViewImpl: UIView { +@objc(RNLiveStreamViewImpl) +public class RNLiveStreamViewImpl: UIView { private var liveStream: ApiVideoLiveStream! private var isStreaming: Bool = false @@ -103,13 +104,13 @@ class RNLiveStreamViewImpl: UIView { } } - @objc var audio: NSDictionary = [:] { + @objc public var audio: NSDictionary = [:] { didSet { audioConfig = AudioConfig(bitrate: audio["bitrate"] as! Int) } } - @objc var video: NSDictionary = [:] { + @objc public var video: NSDictionary = [:] { didSet { if isStreaming { videoBitrate = video["bitrate"] as! Int @@ -122,7 +123,7 @@ class RNLiveStreamViewImpl: UIView { } } - @objc var camera: String { + @objc public var camera: String { get { return liveStream.cameraPosition.toCameraPositionName() } @@ -135,7 +136,7 @@ class RNLiveStreamViewImpl: UIView { } } - @objc var isMuted: Bool { + @objc public var isMuted: Bool { get { return liveStream.isMuted } @@ -147,7 +148,7 @@ class RNLiveStreamViewImpl: UIView { } } - @objc var zoomRatio: Float { + @objc public var zoomRatio: Float { get { return Float(liveStream.zoomRatio) } @@ -156,7 +157,7 @@ class RNLiveStreamViewImpl: UIView { } } - @objc var enablePinchedZoom: Bool { + @objc public var enablePinchedZoom: Bool { get { return zoomGesture.isEnabled } @@ -165,7 +166,7 @@ class RNLiveStreamViewImpl: UIView { } } - @objc func startStreaming(_ streamKey: String, url: String? = nil) throws { + @objc public func startStreaming(_ streamKey: String, url: String?) throws { if let url = url { try liveStream.startStreaming(streamKey: streamKey, url: url) } else { @@ -174,15 +175,13 @@ class RNLiveStreamViewImpl: UIView { isStreaming = true } - @objc func stopStreaming() { + @objc public func stopStreaming() { isStreaming = false liveStream.stopStreaming() } - @objc func setZoomRatio(zoomRatio: CGFloat) { - if let liveStream = liveStream { - liveStream.zoomRatio = zoomRatio - } + @objc public func setZoomRatio(zoomRatio: CGFloat) { + liveStream.zoomRatio = zoomRatio } @objc @@ -193,11 +192,11 @@ class RNLiveStreamViewImpl: UIView { } } - @objc var onConnectionSuccess: RCTDirectEventBlock? + @objc public var onConnectionSuccess: () -> Void = { } - @objc var onConnectionFailed: RCTDirectEventBlock? + @objc public var onConnectionFailed: (String) -> Void = { _ in } - @objc var onDisconnect: RCTDirectEventBlock? + @objc public var onDisconnect: () -> Void = { } @objc override public func removeFromSuperview() { super.removeFromSuperview() @@ -205,29 +204,28 @@ class RNLiveStreamViewImpl: UIView { } } + extension RNLiveStreamViewImpl: ApiVideoLiveStreamDelegate { /// Called when the connection to the rtmp server is successful - func connectionSuccess() { - onConnectionSuccess?([:]) + public func connectionSuccess() { + onConnectionSuccess() } /// Called when the connection to the rtmp server failed - func connectionFailed(_ code: String) { + public func connectionFailed(_ code: String) { isStreaming = false - onConnectionFailed?([ - "code": code, - ]) + onConnectionFailed(code) } /// Called when the connection to the rtmp server is closed - func disconnection() { + public func disconnection() { isStreaming = false - onDisconnect?([:]) + onDisconnect() } /// Called if an error happened during the audio configuration - func audioError(_: Error) {} + public func audioError(_: Error) {} /// Called if an error happened during the video configuration - func videoError(_: Error) {} + public func videoError(_: Error) {} } diff --git a/ios/RNLiveStreamViewManager.m b/ios/RNLiveStreamViewManager.mm similarity index 71% rename from ios/RNLiveStreamViewManager.m rename to ios/RNLiveStreamViewManager.mm index 4168d6d..8e12168 100644 --- a/ios/RNLiveStreamViewManager.m +++ b/ios/RNLiveStreamViewManager.mm @@ -1,9 +1,5 @@ #import "React/RCTViewManager.h" -#ifdef RCT_NEW_ARCH_ENABLED -#import "ApiVideoLiveStreamViewSpec/ApiVideoLiveStreamViewSpec.h" -#endif - @interface RCT_EXTERN_REMAP_MODULE(ApiVideoLiveStreamView, RNLiveStreamViewManager, RCTViewManager) RCT_EXPORT_VIEW_PROPERTY(audio, NSDictionary) @@ -28,13 +24,4 @@ @interface RCT_EXTERN_REMAP_MODULE(ApiVideoLiveStreamView, RNLiveStreamViewManag (nonnull NSNumber *)reactTag withZoomRatio:(nonnull NSNumber *)zoomRatio) -// Thanks to this guard, we won't compile this code when we build for the old architecture. -#ifdef RCT_NEW_ARCH_ENABLED -- (std::shared_ptr)getTurboModule: - (const facebook::react::ObjCTurboModule::InitParams &)params -{ - return std::make_shared(params); -} -#endif - @end diff --git a/ios/RNLivestream.xcodeproj/project.pbxproj b/ios/RNLivestream.xcodeproj/project.pbxproj index 98470d7..77733f0 100644 --- a/ios/RNLivestream.xcodeproj/project.pbxproj +++ b/ios/RNLivestream.xcodeproj/project.pbxproj @@ -6,12 +6,6 @@ objectVersion = 46; objects = { -/* Begin PBXBuildFile section */ - 21237D972A866EFE00A5CDCD /* RNLiveStreamViewManager.m in Sources */ = {isa = PBXBuildFile; fileRef = 21237D942A866EFE00A5CDCD /* RNLiveStreamViewManager.m */; }; - 21237D982A866EFE00A5CDCD /* RNLiveStreamViewManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 21237D952A866EFE00A5CDCD /* RNLiveStreamViewManager.swift */; }; - 21237D992A866EFE00A5CDCD /* RNLiveStreamView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 21237D962A866EFE00A5CDCD /* RNLiveStreamView.swift */; }; -/* End PBXBuildFile section */ - /* Begin PBXCopyFilesBuildPhase section */ 58B511D91A9E6C8500147676 /* CopyFiles */ = { isa = PBXCopyFilesBuildPhase; @@ -27,9 +21,11 @@ /* Begin PBXFileReference section */ 134814201AA4EA6300B7C361 /* libRNLivestream.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = libRNLivestream.a; sourceTree = BUILT_PRODUCTS_DIR; }; 21237D932A866EFD00A5CDCD /* RNLivestream-Bridging-Header.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "RNLivestream-Bridging-Header.h"; sourceTree = ""; }; - 21237D942A866EFE00A5CDCD /* RNLiveStreamViewManager.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RNLiveStreamViewManager.m; sourceTree = ""; }; - 21237D952A866EFE00A5CDCD /* RNLiveStreamViewManager.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = RNLiveStreamViewManager.swift; sourceTree = ""; }; - 21237D962A866EFE00A5CDCD /* RNLiveStreamView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = RNLiveStreamView.swift; sourceTree = ""; }; + 212E11F62B4468BC00FFDE96 /* RNLiveStreamViewManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RNLiveStreamViewManager.swift; sourceTree = ""; }; + 212E11F72B4468BC00FFDE96 /* RNLiveStreamViewManager.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = RNLiveStreamViewManager.m; sourceTree = ""; }; + 212E11F82B4468BD00FFDE96 /* RNLiveStreamViewImpl.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RNLiveStreamViewImpl.swift; sourceTree = ""; }; + 212E11F92B4468BD00FFDE96 /* RNLiveStreamView.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = RNLiveStreamView.m; sourceTree = ""; }; + 212E11FA2B4468BD00FFDE96 /* RNLiveStreamView.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = RNLiveStreamView.h; sourceTree = ""; }; /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ @@ -54,9 +50,11 @@ 58B511D21A9E6C8500147676 = { isa = PBXGroup; children = ( - 21237D962A866EFE00A5CDCD /* RNLiveStreamView.swift */, - 21237D942A866EFE00A5CDCD /* RNLiveStreamViewManager.m */, - 21237D952A866EFE00A5CDCD /* RNLiveStreamViewManager.swift */, + 212E11FA2B4468BD00FFDE96 /* RNLiveStreamView.h */, + 212E11F92B4468BD00FFDE96 /* RNLiveStreamView.m */, + 212E11F82B4468BD00FFDE96 /* RNLiveStreamViewImpl.swift */, + 212E11F72B4468BC00FFDE96 /* RNLiveStreamViewManager.m */, + 212E11F62B4468BC00FFDE96 /* RNLiveStreamViewManager.swift */, 134814211AA4EA7D00B7C361 /* Products */, 21237D932A866EFD00A5CDCD /* RNLivestream-Bridging-Header.h */, ); @@ -120,9 +118,6 @@ isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( - 21237D992A866EFE00A5CDCD /* RNLiveStreamView.swift in Sources */, - 21237D982A866EFE00A5CDCD /* RNLiveStreamViewManager.swift in Sources */, - 21237D972A866EFE00A5CDCD /* RNLiveStreamViewManager.m in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; diff --git a/react-native-livestream.podspec b/react-native-livestream.podspec index a17c558..58075f1 100644 --- a/react-native-livestream.podspec +++ b/react-native-livestream.podspec @@ -31,7 +31,7 @@ Pod::Spec.new do |s| s.pod_target_xcconfig = { "HEADER_SEARCH_PATHS" => "\"$(PODS_ROOT)/boost\"", "OTHER_CPLUSPLUSFLAGS" => "-DFOLLY_NO_CONFIG -DFOLLY_MOBILE=1 -DFOLLY_USE_LIBCPP=1", - "CLANG_CXX_LANGUAGE_STANDARD" => "c++17" + "CLANG_CXX_LANGUAGE_STANDARD" => "c++17", } s.dependency "React-RCTFabric" s.dependency "React-Codegen"