opentok-react-native
Version:
Vonage Video client SDK for React Native
902 lines (726 loc) • 37.7 kB
Markdown
# 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 ¶meters)
: ConcreteComponentDescriptor(parameters) {}
};
class OTRNSubscriberComponentDescriptor final : public ConcreteComponentDescriptor<OTRNSubscriberShadowNode> {
public:
OTRNSubscriberComponentDescriptor(ComponentDescriptorParameters const ¶meters)
: 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
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
OTRNPublisherComponentView : RCTViewComponentView <RCTOTRNPublisherViewProtocol>
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
OpentokReactNative : NSObject <NativeOpentokCxxSpecJSI>
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"
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;
}
```
> 📁 **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>
OpenTokFabricRegistration
+ (void)registerOpenTokComponents {
[RCTComponentViewFactory registerComponentViewClass:[OTPublisherViewNativeComponentView class]];
[RCTComponentViewFactory registerComponentViewClass:[OTSubscriberViewNativeComponentView class]];
}
```
**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.