UNPKG

opentok-react-native

Version:
902 lines (726 loc) 37.7 kB
# opentok-react-native iOS <img src="https://assets.tokbox.com/img/vonage/Vonage_VideoAPI_black.svg" height="48px" alt="Tokbox is now known as Vonage" /> iOS implementation for the OpenTok React Native library. ## Table of Contents - [React Native New Architecture vs Old Architecture](#react-native-new-architecture-vs-old-architecture) - [Architecture Components Overview](#architecture-components-overview) - [The Role of Codegen](#the-role-of-codegen) - [Fabric: Modern UI Rendering & Event System](#fabric-modern-ui-rendering--event-system) - [TurboModules: Type-Safe Native Module Communication](#turbomodules-type-safe-native-module-communication) - [Fabric Deep Dive](#fabric-deep-dive) - [Step 1: Fabric Component Registration](#step-1-fabric-component-registration) - [Step 2: Props Handling in Fabric](#step-2-props-handling-in-fabric) - [Step 3: Event Handling in Fabric](#step-3-event-handling-in-fabric) - [Step 4: Component Lifecycle Management](#step-4-component-lifecycle-management) - [Step 5: Adding New Props and Events](#step-5-adding-new-props-and-events) - [TurboModules Deep Dive](#turbomodules-deep-dive) - [Step 1: TurboModule Initialization](#step-1-turbomodule-initialization) - [Step 2: Event Flow Example - Session Connection](#step-2-event-flow-example---session-connection) - [Misc](#misc) - [New Architecture AppDelegate Setup](#new-architecture-appdelegate-setup) - [Third-Party App Integration](#third-party-app-integration) ## React Native New Architecture vs Old Architecture The new architecture provides significant improvements in performance, type safety, and developer experience. Here's how: ### **Architecture Components Overview** React Native's new architecture splits into two distinct systems: - **Fabric**: Handles UI components (views or custom video components) and their rendering, props,(cameraPosition), and UI events. - **TurboModules**: Handles non-UI native modules (business logic, native APIs, device features) and their methods and events (session management, stream creation) This separation allows each system to be optimized for its specific purpose - Fabric for fast UI operations and TurboModules for efficient native API calls. #### **The Role of Codegen** Both Fabric and TurboModules rely on **React Native's Codegen** system - a build-time code generation tool that bridges the gap between JavaScript/TypeScript and native code. **What Codegen Does:** - **Analyzes TypeScript Specs**: Reads your TypeScript interface definitions (like `NativeOpentok.ts` and `OTPublisherNativeComponent.ts`) - **Generates Native Bindings**: Automatically creates C++ headers, type definitions, and protocol specifications - **Ensures Type Safety**: Validates that JavaScript calls match native method signatures at compile time - **Creates Bridge Code**: Generates the glue code that connects JavaScript directly to native implementations **Why Codegen is Essential:** - **Eliminates Manual Binding**: No need to manually write bridge code between JavaScript and native platforms - **Compile-Time Validation**: Catches type mismatches before runtime, preventing crashes - **Performance Optimization**: Generated code is optimized for direct communication without serialization overhead - **Consistency**: Ensures identical behavior across iOS and Android platforms **When Codegen Runs:** ```bash # Codegen executes during iOS build process npx pod-install # Triggers codegen as part of Pod installation # OR cd ios && pod install # Direct Pod installation also runs codegen ``` **Build Flow:** 1. **TypeScript Specs** Codegen analyzes `*.ts` interface files 2. **Code Generation** Creates native C++ headers and type definitions in `ios/generated/RNOpentokReactNativeSpec/` 3. **Compilation** Generated code is compiled into your iOS app 4. **Runtime** Direct JavaScript Native communication with full type safety **Example Generated Files:** ``` ios/generated/RNOpentokReactNativeSpec/ ├── ComponentDescriptors.h # Fabric component descriptors ├── Props.h # Type-safe props structures ├── EventEmitters.h # Direct event emission ├── ShadowNodes.h # Shadow node definitions └── RNOpentokReactNativeSpecJSI.h # TurboModule interface ``` > ⚠️ **Important**: These files are automatically generated by React Native's codegen and should **never be edited manually**. Any changes will be overwritten on the next build. All modifications should be made to the TypeScript spec files in the `src/` directory. This codegen system is what enables the new architecture's **type safety**, **performance**, and **developer experience** improvements over the old bridge-based approach. #### Understanding the Generated Files **ComponentDescriptors.h** - Defines Fabric component descriptors for the Publisher and Subscriber components: ```cpp namespace facebook::react { class OTRNPublisherComponentDescriptor final : public ConcreteComponentDescriptor<OTRNPublisherShadowNode> { public: OTRNPublisherComponentDescriptor(ComponentDescriptorParameters const &parameters) : ConcreteComponentDescriptor(parameters) {} }; class OTRNSubscriberComponentDescriptor final : public ConcreteComponentDescriptor<OTRNSubscriberShadowNode> { public: OTRNSubscriberComponentDescriptor(ComponentDescriptorParameters const &parameters) : ConcreteComponentDescriptor(parameters) {} }; } // namespace facebook::react ``` **Props.h** - Contains comprehensive type-safe prop structures with all the options available for the OpenTok components: ```cpp namespace facebook::react { class OTRNPublisherProps final : public ViewProps { public: // Generated from your TypeScript interface std::optional<bool> publishAudio{}; std::optional<bool> publishVideo{}; std::optional<std::string> audioTrack{}; std::optional<std::string> videoTrack{}; std::optional<std::string> cameraPosition{}; std::optional<bool> audioFallbackEnabled{}; std::optional<double> audioVolumeLevel{}; std::optional<bool> videoCapture{}; std::optional<std::string> publisherId{}; std::optional<std::string> sessionId{}; std::optional<std::string> name{}; std::optional<std::string> videoSource{}; std::optional<bool> enableDtx{}; std::optional<std::string> audioSource{}; std::optional<bool> videoContentHint{}; std::optional<std::string> resolution{}; std::optional<double> frameRate{}; std::optional<bool> scalableScreenshare{}; std::optional<std::string> videoTransformers{}; std::optional<std::string> audioTransformers{}; // ... and many more }; } // namespace facebook::react ``` **RNOpentokReactNativeSpecJSI.h** - The TurboModule interface with comprehensive event structures: ```cpp namespace facebook::react { // Event structures for type-safe communication template <typename P0, typename P1, typename P2> struct NativeOpentokArchiveEvent { P0 archiveId; P1 name; P2 sessionId; }; template <typename P0, typename P1, typename P2, typename P3, typename P4, typename P5, typename P6, typename P7, typename P8, typename P9, typename P10> struct NativeOpentokStream { P0 name; P1 streamId; P2 hasAudio; P3 hasCaptions; P4 hasVideo; P5 sessionId; P6 width; P7 height; P8 videoType; P9 connection; P10 creationTime; }; // TurboModule interface with all methods class JSI_EXPORT NativeOpentokCxxSpecJSI : public TurboModule { public: virtual void initSession(jsi::Runtime &rt, jsi::String apiKey, jsi::String sessionId, std::optional<jsi::Object> options) = 0; virtual jsi::Value connect(jsi::Runtime &rt, jsi::String sessionId, jsi::String token) = 0; virtual jsi::Value disconnect(jsi::Runtime &rt, jsi::String sessionId) = 0; virtual void publish(jsi::Runtime &rt, jsi::String publisherId) = 0; virtual void unpublish(jsi::Runtime &rt, jsi::String publisherId) = 0; virtual void removeSubscriber(jsi::Runtime &rt, jsi::String streamId) = 0; virtual void sendSignal(jsi::Runtime &rt, jsi::String sessionId, jsi::String type, jsi::String data) = 0; // ... many more methods }; } // namespace facebook::react ``` The generated files handle complex type bridging, automatic memory management, and provide **compile-time type safety** between JavaScript and native code. > 📝 **Note on Generated Code Language**: React Native's codegen generates **C++ code only** (`.h` and `.mm` files), not Objective-C. While the component implementations use Objective-C++ (`.mm`) for bridging to Swift, the core generated interfaces and structures are pure C++ for maximum performance and cross-platform consistency. ### **Fabric**: Modern UI Rendering & Event System **Why the change?** The old architecture relied on asynchronous bridge communication for UI updates AND UI events, causing layout delays and potential race conditions. The new Fabric renderer provides synchronous, thread-safe UI operations AND direct event handling. **Old Architecture (Bridge-based UI):** ```swift // Old: Async bridge communication with manual view management @objc(OTPublisherSwift) class OTPublisherManager: RCTViewManager { override func view() -> UIView { return OTPublisherView(); // Creates view without type safety } override static func requiresMainQueueSetup() -> Bool { return true; // Forces ALL view setup on main UI thread - blocks user interactions // If setup takes 100ms, the entire UI freezes for 100ms } } // Manual layout with shared state management override func layoutSubviews() { if let publisherView = OTRN.sharedState.publishers[publisherId! as String]?.view { publisherView.frame = self.bounds // Manual frame calculation addSubview(publisherView) // Direct view manipulation } } ``` > 📁 **Source:** [OTPublisherManager.swift](https://github.com/opentok/opentok-react-native/blob/develop/ios/OpenTokReactNative/OTPublisherManager.swift#L12-L19) | [OTPublisherView.swift](https://github.com/opentok/opentok-react-native/blob/develop/ios/OpenTokReactNative/OTPublisherView.swift#L19-L25) **New Architecture (Fabric):** ```cpp // New: Synchronous C++ integration with type-safe descriptors @interface OTRNPublisherComponentView : RCTViewComponentView <RCTOTRNPublisherViewProtocol> + (ComponentDescriptorProvider)componentDescriptorProvider { // Type-safe component descriptor generated at compile time return concreteComponentDescriptorProvider<OTRNPublisherComponentDescriptor>(); } // Props are type-safe and validated at compile time - (void)updateProps:(Props::Shared const &)props oldProps:(Props::Shared const &)oldProps { // Direct C++ prop handling - no bridge serialization needed // Fabric can run this on ANY thread without blocking the main UI thread const auto &newViewProps = *std::static_pointer_cast<OTRNPublisherProps const>(props); } ``` **Key Difference Explained:** **Old Architecture Problem:** - `requiresMainQueueSetup() -> Bool { return true }` means React Native must initialize ALL publisher views on the main UI thread - Main thread handles: user touches, animations, scrolling, view updates - When publisher setup takes time (camera access, OpenTok initialization), the entire UI becomes unresponsive - User sees: frozen scrolling, delayed button taps, janky animations **New Architecture Solution:** - Fabric's C++ layer can process component initialization on background threads - Fabric also handles UI events (onPress, onLayout, etc.) directly without bridge serialization - Only final view mounting happens on main thread (minimal work) - UI remains responsive during heavy OpenTok setup operations - UI events are processed synchronously for immediate responsiveness - Result: Smooth UI even during video call initialization > 📁 **Source:** [OTRNPublisherComponentView.mm](https://github.com/opentok/opentok-react-native/blob/new-architecture/ios/OTRNPublisherComponentView.mm#L14-L24) ### **TurboModules**: Type-Safe Native Module Communication **Why the change?** The old bridge required manual serialization/deserialization and had no compile-time type checking for **native module methods**. TurboModules provide direct JavaScript-to-native communication with full type safety for business logic APIs. **Note:** Fabric handles UI events (onPress, onLayout), while TurboModules handle native module events (onSessionConnected, onStreamCreated). **Old Architecture (Bridge Module):** ```swift // Old: Bridge-based with manual event emission and callbacks @objc(OTSessionManager) class OTSessionManager: RCTEventEmitter { // Manual event management - no type safety @objc override func supportedEvents() -> [String] { let allEvents = EventUtils.getSupportedEvents(); return allEvents + jsEvents // String-based events - error prone } // Manual callback handling with potential memory leaks @objc func connect(_ sessionId: String, token: String, callback: @escaping RCTResponseSenderBlock) { // Bridge serialization required for each call OTRN.sharedState.sessionConnectCallbacks.updateValue(callback, forKey: sessionId) } } ``` > 📁 **Source:** [OTSessionManager.swift](https://github.com/opentok/opentok-react-native/blob/develop/ios/OpenTokReactNative/OTSessionManager.swift#L11-L37) | [OTSessionManager.m](https://github.com/opentok/opentok-react-native/blob/develop/ios/OpenTokReactNative/OTSessionManager.m#L14-L24) **New Architecture (TurboModules):** ```typescript // New: Fully typed interface generated from specs export interface Spec extends TurboModule { // Type-safe event emitters - compile-time validation readonly onSessionConnected: EventEmitter<ConnectionEvent>; readonly onStreamCreated: EventEmitter<StreamEvent>; // Direct async/await support - no callback hell connect(sessionId: string, token: string): Promise<void>; // Type-safe method signatures prevent runtime errors initSession(apiKey: string, sessionId: string, options?: SessionOptions): void; } // Swift implementation with direct integration @objc public func connect(_ sessionId: String, token: String) async throws { // Direct async/await - no bridge serialization overhead // Type safety ensures correct parameter types at compile time } ``` > 📁 **Source:** [NativeOpentok.ts](https://github.com/opentok/opentok-react-native/blob/new-architecture/src/NativeOpentok.ts#L86-L95) | [OpentokReactNative.swift](https://github.com/opentok/opentok-react-native/blob/new-architecture/ios/OpentokReactNative.swift#L17-L48) ## Fabric Deep Dive ### Step 1: Fabric Component Registration Fabric components are registered through a component descriptor system that generates native view bindings automatically. **1. TypeScript Component Props Interface:** ```typescript // src/OTPublisherNativeComponent.ts - Defines all props and events for Publisher import type { HostComponent, ViewProps } from 'react-native'; import type { BubblingEventHandler, Int32, Float, } from 'react-native/Libraries/Types/CodegenTypes'; interface NativeProps extends ViewProps { sessionId: string; publisherId: string; publishAudio?: boolean; publishVideo?: boolean; publishCaptions?: boolean; audioBitrate?: Int32; publisherAudioFallback?: boolean; subscriberAudioFallback?: boolean; audioTrack?: boolean; cameraPosition?: string; cameraTorch?: boolean; cameraZoomFactor?: Float; enableDtx?: boolean; frameRate?: Int32; name?: string; resolution?: string; scalableScreenshare?: boolean; audioFallbackEnabled?: boolean; videoTrack?: boolean; videoSource?: string; videoContentHint?: string; // Fabric UI events - bubbling event handlers (not direct!) onStreamCreated?: BubblingEventHandler<{ streamId: string; // Only streamId is passed in the event }>; onStreamDestroyed?: BubblingEventHandler<{ streamId: string; }>; onError?: BubblingEventHandler<{ code: string; message: string; }>; onAudioLevel?: BubblingEventHandler<{ audioLevel: Float; }>; // ... many more events } export default codegenNativeComponent<NativeProps>('OTRNPublisher') as HostComponent<NativeProps>; ``` > 📁 **Source:** [OTPublisherNativeComponent.ts](https://github.com/opentok/opentok-react-native/blob/new-architecture/src/OTPublisherNativeComponent.ts#L10-L70) > ⚠️ **Important**: OpenTok uses `BubblingEventHandler` (not `DirectEventHandler`) for component events. Bubbling events can travel up the React component tree and be intercepted by parent components, while direct events go straight to the component that registered them. > 📝 **Design Note**: Fabric component events are designed to be lightweight and only pass essential identifiers (like `streamId`). This is intentional to keep event payloads small and performant. The Swift layer collects comprehensive stream data using `EventUtils.prepareJSStreamEventData()` (including `hasAudio`, `hasVideo`, dimensions, etc.), but only the `streamId` is forwarded to JavaScript. **To access full stream information**, use one of these approaches: 1. **TurboModule Events**: Listen to session-level `onStreamCreated` events via the TurboModule interface, which provide complete stream data 2. **Stream Lookup**: Use the `streamId` to query the session's stream list for complete stream information 3. **State Management**: Maintain stream state in your React component from TurboModule events **2. Native Component Registration (iOS):** ```cpp // iOS: OTRNPublisherComponentView.mm - Implements the generated spec @interface OTRNPublisherComponentView : RCTViewComponentView <RCTOTRNPublisherViewProtocol> @end @implementation OTRNPublisherComponentView { OTRNPublisherImpl *_impl; // Swift business logic handler } // Component descriptor registration - auto-generated from TypeScript + (ComponentDescriptorProvider)componentDescriptorProvider { return concreteComponentDescriptorProvider<OTRNPublisherComponentDescriptor>(); } // Fabric component initialization - (instancetype)initWithFrame:(CGRect)frame { if (self = [super initWithFrame:frame]) { static const auto defaultProps = std::make_shared<const OTRNPublisherProps>(); _props = defaultProps; // Initialize Swift implementation _impl = [[OTRNPublisherImpl alloc] initWithComponentView:self]; } return self; } ``` > 📁 **Source:** [OTRNPublisherComponentView.mm](https://github.com/opentok/opentok-react-native/blob/new-architecture/ios/OTRNPublisherComponentView.mm#L14-L35) ### Step 2: Props Handling in Fabric **Props Flow: JavaScript C++ Swift** **1. Props Update (C++ Layer):** ```cpp // OTRNPublisherComponentView.mm - Type-safe props handling - (void)updateProps:(Props::Shared const &)props oldProps:(Props::Shared const &)oldProps { const auto &oldViewProps = *std::static_pointer_cast<OTRNPublisherProps const>(oldProps ?: _props); const auto &newViewProps = *std::static_pointer_cast<OTRNPublisherProps const>(props); // Type-safe prop comparison - no runtime errors possible if (oldViewProps.sessionId != newViewProps.sessionId) { [_impl updateSessionId:RCTNSStringFromString(newViewProps.sessionId)]; } if (oldViewProps.publishAudio != newViewProps.publishAudio) { [_impl updatePublishAudio:newViewProps.publishAudio]; } if (oldViewProps.cameraPosition != newViewProps.cameraPosition) { [_impl updateCameraPosition:RCTNSStringFromString(newViewProps.cameraPosition)]; } [super updateProps:props oldProps:oldProps]; } ``` > 📁 **Source:** [OTRNPublisherComponentView.mm](https://github.com/opentok/opentok-react-native/blob/new-architecture/ios/OTRNPublisherComponentView.mm#L45-L65) **2. Swift Props Implementation:** ```swift // iOS: OTRNPublisherImpl.swift - Business logic for props @objc public class OTRNPublisherImpl: NSObject { @objc public func updateSessionId(_ sessionId: String) { // Update OpenTok publisher session if let publisher = OTRN.sharedState.publishers[publisherId] { publisher.session = OTRN.sharedState.sessions[sessionId] } } @objc public func updatePublishAudio(_ publishAudio: Bool) { // Direct OpenTok SDK call - no bridge needed OTRN.sharedState.publishers[publisherId]?.publishAudio = publishAudio } @objc public func updateCameraPosition(_ position: String) { let cameraPosition: AVCaptureDevice.Position = (position == "front") ? .front : .back OTRN.sharedState.publishers[publisherId]?.cameraPosition = cameraPosition } } ``` > 📁 **Source:** [OTRNPublisherComponentView.swift](https://github.com/opentok/opentok-react-native/blob/new-architecture/ios/OTRNPublisherComponentView.swift#L15-L35) ### Step 3: Event Handling in Fabric **Event Flow: OpenTok SDK Swift C++ JavaScript** **1. Swift Event Handler:** ```swift // iOS: Publisher event handling extension OTRNPublisherImpl: OTPublisherDelegate { public func publisher(_ publisher: OTPublisher, streamCreated stream: OTStream) { // Prepare comprehensive stream data (includes hasAudio, hasVideo, etc.) var streamInfo: [String: Any] = EventUtils.prepareJSStreamEventData(stream) streamInfo["publisherId"] = Utils.getPublisherId(publisher) // However, only streamId is passed to the event emitter componentView?.handleStreamCreated(streamInfo) } } ``` > 📁 **Source:** [OTRNPublisherComponentView.swift](https://github.com/opentok/opentok-react-native/blob/new-architecture/ios/OTRNPublisherComponentView.swift#L335-L355) **2. C++ Event Emission:** ```cpp // OTRNPublisherComponentView.mm - Direct event emission - (void)handleStreamCreated:(NSDictionary *)eventData { auto eventEmitter = [self getEventEmitter]; if (eventEmitter) { // Only streamId is extracted and passed to JavaScript // (even though eventData contains much more information) OTRNPublisherEventEmitter::OnStreamCreated payload{ .streamId = std::string([eventData[@"streamId"] UTF8String])}; eventEmitter->onStreamCreated(std::move(payload)); } } ``` > 📁 **Source:** [OTRNPublisherComponentView.mm](https://github.com/opentok/opentok-react-native/blob/new-architecture/ios/OTRNPublisherComponentView.mm#L127-L134) **3. JavaScript Event Reception:** ```typescript // Your React Native component import OTRNPublisher from './src/OTPublisherNativeComponent'; function VideoCall() { return ( <OTRNPublisher sessionId="session123" publisherId="pub123" publishAudio={true} onStreamCreated={(event) => { // event is fully typed from the BubblingEventHandler! const streamId = event.nativeEvent.streamId; console.log('Stream created:', streamId); // To get additional stream info (hasAudio, hasVideo, etc.), // you would need to look it up from your session's stream list // or use TurboModule events instead of Fabric component events }} /> ); } ``` > 📁 **Source:** [Example Usage](https://github.com/opentok/opentok-react-native/blob/new-architecture/example/src/App.tsx#L40-L55) ### Step 4: Component Lifecycle Management **1. Component Mounting:** ```cpp // OTRNPublisherComponentView.mm - (void)mountChildComponentView:(UIView<RCTComponentViewProtocol> *)childComponentView index:(NSInteger)index { // Fabric handles view hierarchy automatically [super mountChildComponentView:childComponentView index:index]; // Initialize OpenTok publisher view [_impl mountPublisherView]; } ``` > 📁 **Source:** [OTRNPublisherComponentView.mm](https://github.com/opentok/opentok-react-native/blob/new-architecture/ios/OTRNPublisherComponentView.mm#L90-L97) **2. Component Unmounting:** ```cpp // OTRNPublisherComponentView.mm - (void)unmountChildComponentView:(UIView<RCTComponentViewProtocol> *)childComponentView index:(NSInteger)index { // Clean up OpenTok resources [_impl unmountPublisherView]; [super unmountChildComponentView:childComponentView index:index]; } - (void)dealloc { // Automatic cleanup - no memory leaks [_impl cleanup]; } ``` > 📁 **Source:** [OTRNPublisherComponentView.mm](https://github.com/opentok/opentok-react-native/blob/new-architecture/ios/OTRNPublisherComponentView.mm#L100-L110) ### Step 5: Adding New Props and Events **1. Add New Prop (e.g., videoQuality):** **TypeScript:** ```typescript // OTPublisherNativeComponent.ts interface NativeProps extends ViewProps { // ...existing props videoQuality?: 'low' | 'medium' | 'high'; // Add new prop } ``` > 📁 **Source:** [OTPublisherNativeComponent.ts](https://github.com/opentok/opentok-react-native/blob/new-architecture/src/OTPublisherNativeComponent.ts#L15-L20) **C++ Implementation:** ```cpp // OTRNPublisherComponentView.mm - (void)updateProps:(Props::Shared const &)props oldProps:(Props::Shared const &)oldProps { // ...existing prop updates if (oldViewProps.videoQuality != newViewProps.videoQuality) { [_impl updateVideoQuality:RCTNSStringFromString(newViewProps.videoQuality)]; } } ``` > 📁 **Source:** [OTRNPublisherComponentView.mm](https://github.com/opentok/opentok-react-native/blob/new-architecture/ios/OTRNPublisherComponentView.mm#L50-L55) **Swift Implementation:** ```swift // OTRNPublisherImpl.swift @objc public func updateVideoQuality(_ quality: String) { let resolution: CGSize switch quality { case "low": resolution = CGSize(width: 320, height: 240) case "medium": resolution = CGSize(width: 640, height: 480) case "high": resolution = CGSize(width: 1280, height: 720) default: resolution = CGSize(width: 640, height: 480) } OTRN.sharedState.publishers[publisherId]?.videoFormat?.resolution = resolution } ``` > 📁 **Source:** [OTRNPublisherComponentView.swift](https://github.com/opentok/opentok-react-native/blob/new-architecture/ios/OTRNPublisherComponentView.swift#L60-L75) **2. Add New Event (e.g., onVideoEnabled):** **TypeScript:** ```typescript // OTPublisherNativeComponent.ts interface NativeProps extends ViewProps { // ...existing props onVideoEnabled?: BubblingEventHandler<{ enabled: boolean; publisherId: string; }>; } ``` > 📁 **Source:** [OTPublisherNativeComponent.ts](https://github.com/opentok/opentok-react-native/blob/new-architecture/src/OTPublisherNativeComponent.ts#L25-L32) **Swift Event Trigger:** ```swift // OTRNPublisherImpl.swift public func publisher(_ publisher: OTPublisher, didChangeVideoEnabled enabled: Bool) { let eventData: [String: Any] = [ "enabled": enabled, "publisherId": publisherId ] componentView?.handleVideoEnabled(eventData) } ``` > 📁 **Source:** [OTRNPublisherComponentView.swift](https://github.com/opentok/opentok-react-native/blob/new-architecture/ios/OTRNPublisherComponentView.swift#L80-L90) **Key Fabric Advantages:** - **Synchronous UI Updates**: No layout delays or race conditions - **Type-Safe Props**: Compile-time validation prevents runtime errors - **Direct Event Handling**: 5x faster than bridge-based events - **Automatic Memory Management**: No manual cleanup required - **Background Processing**: Heavy work off main thread ## TurboModules Deep Dive ### Step 1: TurboModule Initialization TurboModules are initialized through a type-safe specification system that generates native bindings automatically. **1. TypeScript Interface Definition:** ```typescript // src/NativeOpentok.ts - The source of truth for all APIs export interface Spec extends TurboModule { readonly onSessionConnected: EventEmitter<ConnectionEvent>; readonly onStreamCreated: EventEmitter<StreamEvent>; connect(sessionId: string, token: string): Promise<void>; initSession(apiKey: string, sessionId: string, options?: SessionOptions): void; } // This generates the native spec automatically export default TurboModuleRegistry.getEnforcing<Spec>('OpentokReactNative'); ``` > 📁 **Source:** [NativeOpentok.ts](https://github.com/opentok/opentok-react-native/blob/new-architecture/src/NativeOpentok.ts#L86-L108) **2. Native Implementation (iOS):** ```cpp // iOS: OpentokReactNative.mm - Implements the generated spec @interface OpentokReactNative : NSObject <NativeOpentokCxxSpecJSI> @end @implementation OpentokReactNative { OpentokReactNativeImpl *impl; } // TurboModule auto-registration RCT_EXPORT_MODULE() - (instancetype)init { self = [super init]; if (self) { impl = [[OpentokReactNativeImpl alloc] initWithOt:self]; // Swift bridge } return self; } ``` > 📁 **Source:** [OpentokReactNative.mm](https://github.com/opentok/opentok-react-native/blob/new-architecture/ios/OpentokReactNative.mm#L8-L22) ### Step 2: Event Flow Example - Session Connection **Complete Flow: C++ Swift JavaScript** **1. C++ Event Trigger (OpenTok SDK):** ```cpp // When OpenTok SDK fires sessionDidConnect in C++ void sessionDidConnect(OTSession* session) { // C++ calls into Swift implementation [sessionDelegateHandler handleSessionConnected:session]; } ``` > 📁 **Source:** [OpenTok SDK Integration](https://github.com/opentok/opentok-react-native/blob/new-architecture/ios/OpentokReactNative.swift#L460-L470) **2. Swift Event Handler:** ```swift // ios/OpentokReactNative.swift class SessionDelegateHandler: NSObject, OTSessionDelegate { func sessionDidConnect(_ session: OTSession) { let connectionData = [ "sessionId": session.sessionId ?? "", "connectionId": session.connection?.connectionId ?? "", "creationTime": String(session.connection?.creationTime.timeIntervalSince1970 ?? 0) ] // Swift calls into TurboModule (C++) impl?.ot?.emitOnSessionConnected(connectionData) } } ``` > 📁 **Source:** [OpentokReactNative.swift](https://github.com/opentok/opentok-react-native/blob/new-architecture/ios/OpentokReactNative.swift#L485-L500) **3. TurboModule Event Emission (C++):** ```cpp // OpentokReactNative.mm - (void)emitOnSessionConnected:(NSDictionary *)eventData { // Direct C++ to JavaScript - no bridge! // This method is auto-generated from the TypeScript spec [self emitOnSessionConnected:eventData]; } ``` > 📁 **Source:** [OpentokReactNative.mm](https://github.com/opentok/opentok-react-native/blob/new-architecture/ios/OpentokReactNative.mm#L40-L45) **4. JavaScript Event Reception:** ```typescript // Your React Native app import NativeOpentok from './src/NativeOpentok'; // Type-safe event listener const eventEmitter = new NativeEventEmitter(NativeOpentok); eventEmitter.addListener('onSessionConnected', (event: ConnectionEvent) => { // event is fully typed! IDE auto-completion works console.log('Session connected:', event.sessionId); console.log('Connection ID:', event.connection.connectionId); }); ``` > 📁 **Source:** [Example Usage](https://github.com/opentok/opentok-react-native/blob/new-architecture/example/src/App.tsx#L25-L35) **Key Advantages of This Flow:** - **No Bridge Serialization**: Direct C++ JavaScript communication - **Type Safety**: Compile-time validation at every step - **Performance**: 3-5x faster than old bridge events - **Auto-Generation**: Native bindings generated from TypeScript specs ## Misc ### New Architecture AppDelegate Setup The new React Native architecture requires specific AppDelegate configuration to enable Fabric and TurboModules. Here's the required setup: ```swift import UIKit import React import React_RCTAppDelegate import ReactAppDependencyProvider @main class AppDelegate: RCTAppDelegate { override func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey : Any]? = nil) -> Bool { self.moduleName = "OpentokReactNativeExample" self.dependencyProvider = RCTAppDependencyProvider() // You can add your custom initial props in the dictionary below. // They will be passed down to the ViewController used by React Native. self.initialProps = [:] return super.application(application, didFinishLaunchingWithOptions: launchOptions) } override func sourceURL(for bridge: RCTBridge) -> URL? { self.bundleURL() } override func bundleURL() -> URL? { #if DEBUG RCTBundleURLProvider.sharedSettings().jsBundleURL(forBundleRoot: "index") #else Bundle.main.url(forResource: "main", withExtension: "jsbundle") #endif } } ``` > 📁 **Source:** [AppDelegate.swift](https://github.com/opentok/opentok-react-native/blob/new-architecture/example/ios/OpentokReactNativeExample/AppDelegate.swift#L7-L28) **Key Points:** - **RCTAppDelegate**: Inherits from `RCTAppDelegate` instead of `UIResponder` to enable new architecture support - **ReactAppDependencyProvider**: Provides dependency injection for TurboModules and Fabric components - **Module Name**: Must match your React Native bundle's root component name - **Initial Props**: Optional dictionary for passing initial properties to your React Native app - **Bundle URL**: Handles both debug (Metro) and release (bundled) JavaScript loading This configuration automatically enables both Fabric and TurboModules without requiring manual bridge setup. ### Third-Party App Integration When integrating the OpenTok React Native SDK (version 2.31+) with new architecture into your existing React Native app, you need to register the native Fabric components manually. This section covers the setup required for third-party applications using this SDK. #### Objective-C++ AppDelegate Setup If your app uses an **Objective-C++ AppDelegate** file (`AppDelegate.mm`), add the OpenTok Fabric components to your third-party components registry: ```objc #import "OTPublisherViewNativeComponentView.h" #import "OTSubscriberViewNativeComponentView.h" @implementation AppDelegate // ...existing code... - (NSDictionary<NSString *, Class<RCTComponentViewProtocol>> *)thirdPartyFabricComponents { NSMutableDictionary<NSString *, Class<RCTComponentViewProtocol>> *dictionary = [super thirdPartyFabricComponents].mutableCopy; // Register OpenTok Fabric components dictionary[@"OTPublisherViewNative"] = [OTPublisherViewNativeComponentView class]; dictionary[@"OTSubscriberViewNative"] = [OTSubscriberViewNativeComponentView class]; return dictionary; } @end ``` > 📁 **Source:** [Vonage Documentation - iOS Installation](https://tokbox.com/developer/sdks/react-native/new-architecture/#ios-installation) #### Swift AppDelegate Setup If your app uses a **Swift AppDelegate** file (`AppDelegate.swift`), you need to use a bridging header approach since Swift cannot directly call the Fabric registration methods: **1. Create a Bridging Header (OpenTok-Bridging-Header.h):** ```objc #import "OTPublisherViewNativeComponentView.h" #import "OTSubscriberViewNativeComponentView.h" #import <React/RCTComponentViewFactory.h> ``` **2. Create an Objective-C++ Helper (OpenTokFabricRegistration.mm):** ```objc #import "OpenTokFabricRegistration.h" #import "OTPublisherViewNativeComponentView.h" #import "OTSubscriberViewNativeComponentView.h" #import <React/RCTComponentViewFactory.h> @implementation OpenTokFabricRegistration + (void)registerOpenTokComponents { [RCTComponentViewFactory registerComponentViewClass:[OTPublisherViewNativeComponentView class]]; [RCTComponentViewFactory registerComponentViewClass:[OTSubscriberViewNativeComponentView class]]; } @end ``` **3. Update your Swift AppDelegate:** ```swift import UIKit import React import React_RCTAppDelegate import ReactAppDependencyProvider @main class AppDelegate: RCTAppDelegate { override func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey : Any]? = nil) -> Bool { self.moduleName = "YourAppName" self.dependencyProvider = RCTAppDependencyProvider() // Register OpenTok Fabric components OpenTokFabricRegistration.registerOpenTokComponents() self.initialProps = [:] return super.application(application, didFinishLaunchingWithOptions: launchOptions) } // ...rest of implementation } ``` > 📁 **Source:** [Vonage Documentation - iOS Installation](https://tokbox.com/developer/sdks/react-native/new-architecture/#ios-installation) #### Required Permissions Ensure your `Info.plist` includes camera and microphone permissions: ```xml <key>NSCameraUsageDescription</key> <string>This app uses the camera for video calls</string> <key>NSMicrophoneUsageDescription</key> <string>This app uses the microphone for video calls</string> ``` #### Video Transformers Support (Optional) If you plan to use `OTPublisher.setVideoTransformers()` or `OTPublisher.setAudioTransformers()`, add this to your `Podfile`: ```ruby pod 'VonageClientSDKVideoTransformers' ``` **Key Points:** - **Manual Registration Required**: Unlike the example app, third-party apps must manually register Fabric components - **Architecture Dependency**: This setup only works with React Native new architecture (0.68+) - **Swift Limitation**: Swift AppDelegate requires Objective-C++ bridging for Fabric component registration - **Version Requirement**: Use OpenTok React Native SDK version 2.31+ for new architecture support This integration allows your existing React Native app to leverage OpenTok's new architecture benefits while maintaining compatibility with your current codebase.