关于使用 React Native (RN) 构建千万级用户体量视频物联网 (Video IoT) App 的深度的、全栈式的技术方案剖析。

这个方案的核心哲学是:使用 RN 统一 UI 业务逻辑,通过“原生桥接 (Native Bridge)”和“原生模块 (Native Modules)”将极致性能和稳定性要求最高的核心音视频功能下沉到 Android (Kotlin/Java) 和 iOS (Swift/Obj-C) 原生层实现。

这是唯一能同时满足“多端代码复用”和“千万级用户体验”的成熟路径。


视频物联网 App RN 双端适配与原生核心实现深度剖析

第一部分:整体技术架构与跨端策略

视频物联网 App 的复杂性在于它融合了:

  1. 移动端 UI/UX (RN 的强项)
  2. 物联网设备通信协议 (MQTT, CoAP, HTTPS)
  3. 核心音视频流媒体处理 (WebRTC, RTSP/RTMP, H.264/H.265 编解码, 原生核心)

1.1 总体架构图

%% App 层 (React Native)
subgraph App_Layer [React Native App (JS/TS)]
    UI[UI Components (Login, DeviceList, PlayerUI)]
    Redux[State Management (Redux/Zustand)]
    Logic[Business Logic (Device Mgmt, User Auth)]
end

%% 原生桥接层 (The Core Strategy)
subgraph Bridge_Layer [Cross-Platform Native Bridge]
    RN_Bridge[React Native Bridge / JSI]
    subgraph Native_Modules [Target Native Modules]
        AV_NM[Audio/Video Native Module (Custom)]
        IoT_NM[IoT Control Native Module (Custom/SDK)]
        Auth_NM[Secure Storage/Auth Native Module]
    end
end

%% 原生 Android 实现层
subgraph Android_Native [Android Platform (Kotlin/Java)]
    Android_AV[Core AV: WebRTC-Android, ExoPlayer]
    Android_IoT[IoT: MQTT-Android, CoAP]
    Android_OS[OS: Camera2, AudioRecord, MediCodec]
end

%% 原生 iOS 实现层
subgraph iOS_Native [iOS Platform (Swift/Obj-C)]
    iOS_AV[Core AV: WebRTC-iOS, AVFoundation]
    iOS_IoT[IoT: CocoaMQTT, CoAP-iOS]
    iOS_OS[OS: AVFoundation, VideoToolbox]
end

%% 外部服务层
subgraph External_Services [Cloud & Hardware]
    IoT_Cloud[IoT Cloud Service (AWS IoT, Aliyun IoT, etc.)]
    Signaling[WebRTC Signaling Server]
    STUN_TURN[STUN/TURN Servers]
    Device[IoT Camera Hardware]
end

%% 连接关系
UI --> RN_Bridge
RN_Bridge --> AV_NM
RN_Bridge --> IoT_NM
RN_Bridge --> Auth_NM

AV_NM -->|Calls| Android_AV
IoT_NM -->|Calls| Android_IoT
AV_NM -->|Calls| iOS_AV
IoT_NM -->|Calls| iOS_IoT

Android_AV -->|Drivers| Android_OS
iOS_AV -->|Drivers| iOS_OS

Android_IoT -->|Protocol| IoT_Cloud
iOS_IoT -->|Protocol| IoT_Cloud
Android_AV -->|Media P2P/Stream| Device
iOS_AV -->|Media P2P/Stream| Device

Device -->|Status/Control| IoT_Cloud

Android_AV -.->|Signaling| Signaling
iOS_AV -.->|Signaling| Signaling

1.2 核心跨端适配策略深度剖析

1.2.1 UI 与业务逻辑层:React Native
  • 原则:除音视频渲染视窗和极个别特定硬件交互外,100% 的 UI 和业务逻辑(用户登录、设备列表展示、设置界面、控制指令下发)都使用 RN 编写。

  • 千万级适配关键点

  • 性能优化:必须极其关注 RN 的性能。

  • 使用 FlatList 优化长列表。

  • 避免在渲染循环中进行复杂的逻辑计算。

  • 使用 React.memo, useMemo, useCallback 减少不必要的重渲染。

  • 响应式布局:全面使用 Flexbox 和 PixelRatio 来适配各种 Android 和 iOS 屏幕尺寸和分辨率。

1.2.2 状态管理:Zustand 或 Redux

对于千万级用户体量,状态管理必须高效且可预测。

  • 策略:将设备状态(在线/离线、报警状态)和用户信息(Token、权限)集中管理。
  • 关键点:物联网设备状态更新频繁。建议使用轻量级的 Zustand,并配合 Immutable.js 或 Immer,避免复杂的深拷贝操作影响 UI。
1.2.3 物联网通信 (Control Plane):Native Bridge

移动端与物联网云平台的控制面通信(如下发“转动摄像头”指令、获取历史报警记录)通常不要求极低的延迟,但需要高可靠性。

  • 最佳实践:不要在 RN (JS) 层直接使用 Node.js 的 MQTT 库。
  • 原生做法:在 Android 和 iOS 原生层分别封装成熟的 MQTT (如 Eclipse Paho for Android, CocoaMQTT for iOS) 库,然后通过 RN 的 Native Modules 暴露出干净的异步接口给 RN 层调用。
1.2.4 核心音视频 (Data Plane):原生核心下沉 (Key Path)

这是最关键的部分。在 RN (JS) 层处理每秒数兆的音视频流是不现实的。 千万级用户要求:极速秒开、低延时(<200ms)、无卡顿、高编解码效率。

  • 技术选择

  • P2P 双向对讲WebRTC (Google 官方库的原生版本) 是不二之选。

  • HLS/RTSP 单向点播/回看:原生播放器 (iOS 的 AVPlayer, Android 的 ExoPlayer) 是最稳定的。

  • 技术实现细节

  1. 原生模块创建:分别创建 AVNativeModule.kt (Android) 和 AVNativeModule.m/.swift (iOS)。
  2. 核心库接入:在 Android 接入原生的 WebRTC for Android AAR 包;在 iOS 通过 CocoaPods 接入 WebRTC.framework。
  3. 音视频采集与编码:使用原生的 Camera2/AVFoundation 进行采集,使用 MediaCodec/VideoToolbox 进行硬编码。
  4. 渲染桥接:创建一个自定义的 RN View Component (例如 <RTCViewNative />)。
  • Android:将原生的 SurfaceViewTextureView 桥接给 RN。WebRTC 解码后的视频帧将直接绘制在这个 Surface 上,不经过 JS 层。
  • iOS:将原生的 RTCEAGLVideoViewRTCMTLVideoView 桥接给 RN。
  1. 桥接接口封装:给 RN 暴露简洁的方法,如 startCall(deviceId)stopCall()muteMicrophone(isMuted),以及回调事件如 onRemoteStreamReadyonConnectionFailed
1.2.5 编译与发布流程 (CI/CD for Millions)

对于千万级用户,编译和发布不能依赖手动。

  • 策略:构建完善的 CI/CD 流程 (例如使用 GitHub Actions, GitLab CI, Fastlane)。
  • 自动化:自动运行 JS 单元测试、原生 Android/iOS 单元测试、端到端 (E2E) 测试(如 Detox),自动生成 Android AAB 和 iOS IPA 签名包,并上传到 App Store Connect 和 Google Play Console。

第二部分:千万级生产环境音视频核心实现 Demo 剖析 (基于 WebRTC)

这个 Demo 的核心目标是展示如何通过 RN 的自定义 Native Module 将一个完全原生的 WebRTC 呼叫/接收流程暴露给 RN。这不是一个完整的、可直接从 GitHub 克隆的仓库,而是一个详细的代码层面的核心架构剖析,你可以据此构建你的生产环境代码。

2.1 Demo 目标

  1. RN 发起呼叫:用户在 RN 界面点击“呼叫”,RN 调用原生模块方法。
  2. 原生 WebRTC 连接:原生层处理信令交互(示意,需配合云端信令服务器)、建立 P2P 连接、设置 PeerConnection。
  3. 原生渲染:将远程视频流直接在原生创建的 View 上渲染,并把这个 View 桥接到 RN 的布局中。
  4. 双端适配:提供 Android Kotlin 和 iOS Swift 的核心代码结构。

2.2 React Native (JS/TS) 层实现

这是千万级 App 的 UI。

2.2.1 定义 Native Module 接口 (AVNativeModule.ts)
// AVNativeModule.ts - 类型定义文件

import { NativeModules, NativeEventEmitter, ViewProps } from 'react-native';

// 1. 定义原生音视频模块暴露出的方法
interface AVNativeModuleInterface {
  startCall(deviceId: string): Promise<void>;
  stopCall(): void;
  muteMicrophone(isMuted: boolean): void;
}

// 2. 获取原生模块实例 (Android/iOS 必须对应此名称)
const { AVNativeModule } = NativeModules;

// 3. 定义原生事件发射器
const AVNativeEventEmitter = new NativeEventEmitter(AVNativeModule);

// 4. 定义自定义原生视图组件,用于 RN 布局
// 这个组件在底层对应 Android 的 TextureView 和 iOS 的 RTCMTLVideoView
interface RTCViewNativeProps extends ViewProps {
  onRemoteStreamAvailable?: () => void; // 可选的远程流就绪回调
}
const RTCViewNative = requireNativeComponent<RTCViewNativeProps>('RTCViewNative');

export { AVNativeModule, AVNativeEventEmitter, RTCViewNative };

2.2.2 UI 呼叫页面 (VideoCallScreen.tsx)
// VideoCallScreen.tsx - RN 业务逻辑与 UI

import React, { useState, useEffect } from 'react';
import { View, Text, Button, StyleSheet, Alert } from 'react-native';
import { AVNativeModule, AVNativeEventEmitter, RTCViewNative } from './AVNativeModule';

const VideoCallScreen = ({ route }) => {
  const { deviceId } = route.params || { deviceId: 'dummy-camera-1' };
  const [isCalling, setIsCalling] = useState(false);
  const [isRemoteStreamReady, setIsRemoteStreamReady] = useState(false);

  useEffect(() => {
    // 监听原生层发出的事件
    const callStatusListener = AVNativeEventEmitter.addListener('onCallStatusChanged', (status) => {
      console.log('Call status from native:', status);
      if (status === 'connected') {
        setIsRemoteStreamReady(true);
      } else if (status === 'failed' || status === 'disconnected') {
        setIsCalling(false);
        setIsRemoteStreamReady(false);
        Alert.alert('呼叫失败', '连接断开');
      }
    });

    // 清理函数
    return () => {
      callStatusListener.remove();
      AVNativeModule.stopCall(); // 退出页面时自动断开
    };
  }, []);

  const handleStartCall = async () => {
    try {
      setIsCalling(true);
      await AVNativeModule.startCall(deviceId);
    } catch (error) {
      console.error('Error starting call:', error);
      setIsCalling(false);
      Alert.alert('呼叫错误', '无法发起呼叫');
    }
  };

  const handleStopCall = () => {
    AVNativeModule.stopCall();
    setIsCalling(false);
    setIsRemoteStreamReady(false);
  };

  return (
    <View style={styles.container}>
      <Text style={styles.title}>设备呼叫: {deviceId}</Text>
      
      {/* 核心:原生视频渲染视图,在 RN 布局中定义 */}
      {isCalling && (
        <View style={styles.videoWrapper}>
          <RTCViewNative 
            style={styles.remoteVideo} 
            onRemoteStreamAvailable={() => console.log('RN: Remote stream available')}
          />
        </View>
      )}

      <View style={styles.controls}>
        {!isCalling ? (
          <Button title="发起呼叫 (P2P)" onPress={handleStartCall} color="#2196F3" />
        ) : (
          <Button title="挂断" onPress={handleStopCall} color="#F44336" />
        )}
      </View>
    </View>
  );
};

const styles = StyleSheet.create({
  container: { flex: 1, backgroundColor: '#f0f0f0', alignItems: 'center', justifyContent: 'center' },
  title: { fontSize: 18, marginBottom: 20 },
  videoWrapper: { width: '90%', height: 300, backgroundColor: 'black', marginBottom: 20, borderRadius: 10, overflow: 'hidden' },
  remoteVideo: { flex: 1, width: '100%', height: '100%' }, // 这里是原生视图
  controls: { flexDirection: 'row', gap: 20 },
});

export default VideoCallScreen;


2.3 Android 原生实现 (Kotlin)

这是千万级性能的核心。

2.3.1 原生模块核心逻辑 (AVNativeModule.kt)
// Android/app/src/main/java/com/yourcompany/app/avnative/AVNativeModule.kt

package com.yourcompany.app.avnative

import com.facebook.react.bridge.*
import com.facebook.react.modules.core.DeviceEventManagerModule
import org.webrtc.*
import java.util.*

class AVNativeModule(reactContext: ReactApplicationContext) : ReactContextBaseJavaModule(reactContext) {

    private var peerConnectionFactory: PeerConnectionFactory? = null
    private var peerConnection: PeerConnection? = null
    private var localAudioSource: AudioSource? = null
    private var localAudioTrack: AudioTrack? = null
    private var isCallInProgress = false

    override fun getName(): String {
        return "AVNativeModule" // 必须与 RN 中 requireNativeComponent 的名称一致
    }

    @ReactMethod
    fun startCall(deviceId: String, promise: Promise) {
        if (isCallInProgress) {
            promise.reject("CALL_IN_PROGRESS", "A call is already in progress.")
            return
        }

        try {
            initWebRTC()
            // 1. 初始化 WebRTC
            // 2. 创建 PeerConnection
            // 3. 将本地音频/视频流添加到 PeerConnection (如果是对讲)
            // 4. 创建 Offer (或等待 Answer)
            // 5. 你的信令服务器逻辑(连接,发送 Offer)
            // 6. 如果连接成功,向 RN 发送事件
            sendEvent("onCallStatusChanged", "connecting")
            
            // 这里我们模拟一个成功的 P2P 连接建立
            Timer().schedule(object : TimerTask() {
                override fun run() {
                    sendEvent("onCallStatusChanged", "connected")
                }
            }, 1500)
            
            isCallInProgress = true
            promise.resolve(null)
        } catch (e: Exception) {
            promise.reject("CALL_FAILED", "Failed to start call: ${e.message}")
        }
    }

    @ReactMethod
    fun stopCall() {
        // 关闭 PeerConnection
        peerConnection?.close()
        peerConnection = null
        // 销毁 Factory
        peerConnectionFactory?.dispose()
        peerConnectionFactory = null
        isCallInProgress = false
        sendEvent("onCallStatusChanged", "disconnected")
    }

    @ReactMethod
    fun muteMicrophone(isMuted: boolean) {
        localAudioTrack?.setEnabled(!isMuted)
    }

    // 内部方法:向 RN 发送事件
    private fun sendEvent(eventName: String, params: Any?) {
        reactApplicationContext
            .getJSModule(DeviceEventManagerModule.RCTDeviceEventEmitter::class.java)
            .emit(eventName, params)
    }

    // 这里包含 WebRTC 的所有核心原生设置
    private fun initWebRTC() {
        // WebRTC 原生设置
        val eglBase = EglBase.create()
        PeerConnectionFactory.initialize(PeerConnectionFactory.InitializationOptions.builder(reactApplicationContext).createInitializationOptions())
        val options = PeerConnectionFactory.Options()
        val defaultVideoEncoderFactory = DefaultVideoEncoderFactory(eglBase.eglBaseContext, true, true)
        val defaultVideoDecoderFactory = DefaultVideoDecoderFactory(eglBase.eglBaseContext)
        peerConnectionFactory = PeerConnectionFactory.builder()
            .setOptions(options)
            .setVideoEncoderFactory(defaultVideoEncoderFactory)
            .setVideoDecoderFactory(defaultVideoDecoderFactory)
            .createPeerConnectionFactory()
            
        val rtcConfig = PeerConnection.RTCConfiguration(emptyList()) // 生产环境需要设置 STUN/TURN
        
        // 这是一个示意性设置,不包含完整的 Offer/Answer 流程
        peerConnection = peerConnectionFactory?.createPeerConnection(rtcConfig, object : PeerConnection.Observer {
            override fun onIceCandidate(candidate: IceCandidate?) {
                // 发送 Candidate 到信令服务器
            }

            override fun onAddStream(stream: MediaStream?) {
                // 千万级关键点:这里接收到流,需要通知自定义的原生 View 进行渲染
                stream?.videoTracks?.get(0)?.let { remoteVideoTrack ->
                    // 这里我们通过一个单例或者某种方式找到那个自定义的原生 View
                    // 并调用原生渲染方法,将 remoteVideoTrack 绑定给它
                    // 我们稍后在 RTCViewManager 中实现
                }
            }
            // ... 实现了其他 Observer 方法
        })
    }
}

2.3.2 自定义原生渲染视图及其 Manager (RTCViewManager.kt)
// Android/app/src/main/java/com/yourcompany/app/avnative/RTCViewManager.kt

package com.yourcompany.app.avnative

import android.view.View
import com.facebook.react.uimanager.SimpleViewManager
import com.facebook.react.uimanager.ThemedReactContext
import com.facebook.react.uimanager.annotations.ReactProp
import com.facebook.react.common.MapBuilder
import org.webrtc.EglBase
import org.webrtc.SurfaceViewRenderer
import org.webrtc.VideoTrack

class RTCViewManager : SimpleViewManager<SurfaceViewRenderer>() {

    companion object {
        const val REACT_CLASS = "RTCViewNative" // 与 RN 中 requireNativeComponent 的名称一致
        private var sharedEglBase: EglBase? = null // 共享 EGL 上下文,关键性能点
    }

    override fun getName(): String {
        return REACT_CLASS
    }

    override fun createViewInstance(reactContext: ThemedReactContext): SurfaceViewRenderer {
        if (sharedEglBase == null) {
            sharedEglBase = EglBase.create() // 确保在主线程创建
        }
        val view = SurfaceViewRenderer(reactContext)
        view.init(sharedEglBase!!.eglBaseContext, null)
        view.setMirror(true) // 默认开启镜像,如果是前置对讲
        view.setEnableHardwareScaler(true) // 千万级必须开启硬解硬件加速
        
        // 千万级关键点:将该渲染视图注册给一个单例对象
        // 以便 AVNativeModule 在接收到远程流时,能够找到它
        VideoRenderRegistry.registerRemoteView(view)
        
        return view
    }

    override fun onDropViewInstance(view: SurfaceViewRenderer) {
        VideoRenderRegistry.unregisterRemoteView(view)
        view.release()
        super.onDropViewInstance(view)
    }
}

// 简单的单例,用于在原生层进行跨组件的数据流传递
object VideoRenderRegistry {
    private var remoteViewRef: SurfaceViewRenderer? = null
    private var remoteVideoTrackRef: VideoTrack? = null

    fun registerRemoteView(view: SurfaceViewRenderer) {
        remoteViewRef = view
        // 如果流已经准备好,直接绑定
        remoteVideoTrackRef?.addSink(view)
    }

    fun unregisterRemoteView(view: SurfaceViewRenderer) {
        remoteVideoTrackRef?.removeSink(view)
        remoteViewRef = null
    }

    // 这个方法由 AVNativeModule 在 onAddStream 中调用
    fun onRemoteStreamReady(videoTrack: VideoTrack) {
        remoteVideoTrackRef = videoTrack
        remoteViewRef?.let {
            videoTrack.addSink(it)
        }
    }
}

2.3.3 Android 模块注册 (AVPackage.kt)
// Android/app/src/main/java/com/yourcompany/app/avnative/AVPackage.kt

package com.yourcompany.app.avnative

import com.facebook.react.ReactPackage
import com.facebook.react.bridge.NativeModule
import com.facebook.react.bridge.ReactApplicationContext
import com.facebook.react.uimanager.ViewManager

class AVPackage : ReactPackage {
    override fun createNativeModules(reactContext: ReactApplicationContext): List<NativeModule> {
        return listOf(AVNativeModule(reactContext))
    }

    override fun createViewManagers(reactContext: ReactApplicationContext): List<ViewManager<*, *>> {
        return listOf(RTCViewManager())
    }
}

在你的 MainApplication.javagetPackages() 方法中添加 new AVPackage() 即可接入。


2.4 iOS 原生实现 (Swift)

使用 Swift 实现原生部分,代码更简洁。

2.4.1 原生模块核心逻辑 (AVNativeModule.swift)
// iOS/AVNativeModule.swift

import Foundation
import React
import WebRTC

@objc(AVNativeModule)
class AVNativeModule: RCTEventEmitter {

  private var peerConnectionFactory: RTCPeerConnectionFactory?
  private var peerConnection: RTCPeerConnection?
  private var localAudioTrack: RTCAudioTrack?
  private var isCallInProgress = false

  override func getName() -> String! {
    return "AVNativeModule" // 必须与 RN 中 requireNativeComponent 的名称一致
  }
  
  // 指定允许发送给 RN 的事件名称
  override func supportedEvents() -> [String]! {
    return ["onCallStatusChanged"]
  }

  // 必须指定方法在主线程还是后台线程运行
  @objc override static func requiresMainQueueSetup() -> Bool {
    return false // WebRTC 核心逻辑通常运行在自己的后台线程,避免阻塞 UI
  }

  @objc(startCall:resolver:rejecter:)
  func startCall(deviceId: String, resolver: @escaping RCTPromiseResolveBlock, rejecter: @escaping RCTPromiseRejectBlock) {
    if isCallInProgress {
      rejecter("CALL_IN_PROGRESS", "A call is already in progress.", nil)
      return
    }

    do {
      try initWebRTC()
      // 1. 初始化 WebRTC
      // 2. 创建 PeerConnection
      // 3. 将本地音/视频流添加到 PeerConnection
      // 4. 创建 Offer/等待 Answer
      // 5. 你的信令服务器逻辑
      self.sendEvent(withName: "onCallStatusChanged", body: "connecting")
      
      // 这里模拟一个成功的 P2P 连接建立
      DispatchQueue.main.asyncAfter(deadline: .now() + 1.5) {
        self.sendEvent(withName: "onCallStatusChanged", body: "connected")
      }
      
      isCallInProgress = true
      resolver(nil)
    } catch {
      rejecter("CALL_FAILED", "Failed to start call: \(error.localizedDescription)", nil)
    }
  }

  @objc(stopCall)
  func stopCall() {
    // 关闭和清理 PeerConnection
    peerConnection?.close()
    peerConnection = nil
    peerConnectionFactory = nil
    isCallInProgress = false
    self.sendEvent(withName: "onCallStatusChanged", body: "disconnected")
  }

  @objc(muteMicrophone:)
  func muteMicrophone(isMuted: Bool) {
    localAudioTrack?.isEnabled = !isMuted
  }

  // 这里包含 WebRTC 的所有核心原生设置
  private func initWebRTC() throws {
    // 1. 初始化 Factory
    let encoderFactory = RTCDefaultVideoEncoderFactory()
    let decoderFactory = RTCDefaultVideoDecoderFactory()
    peerConnectionFactory = RTCPeerConnectionFactory(encoderFactory: encoderFactory, decoderFactory: decoderFactory)
    
    // 2. 配置 PeerConnection
    let config = RTCConfiguration() // 生产环境需要设置 STUN/TURN
    let constraints = RTCMediaConstraints(mandatoryConstraints: nil, optionalConstraints: nil)
    
    peerConnection = peerConnectionFactory?.peerConnection(with: config, constraints: constraints, delegate: self)
  }
}

// 实现了 WebRTC Delegate 方法
extension AVNativeModule: RTCPeerConnectionDelegate {
  func peerConnection(_ peerConnection: RTCPeerConnection, didAdd stream: RTCMediaStream) {
    // 千万级关键点:这里接收到远程流
    if let remoteVideoTrack = stream.videoTracks.first {
      // 通过单例找到自定义原生视图,并绑定
      VideoRenderRegistry.shared.onRemoteStreamReady(videoTrack: remoteVideoTrack)
    }
  }
  
  // ... 实现了其他 Delegate 方法
}

2.4.2 自定义原生渲染视图 (RTCViewNative.swift) 和 Manager
// iOS/RTCViewNative.swift

import UIKit
import WebRTC
import React

// 自定义视图,用于封装 RTCMTLVideoView
class RTCViewNative: UIView {
  private let videoView: RTCMTLVideoView // 使用 Metal 的硬件加速渲染,性能最佳

  override init(frame: CGRect) {
    self.videoView = RTCMTLVideoView(frame: .zero)
    super.init(frame: frame)
    self.setupView()
  }

  required init?(coder: NSCoder) {
    fatalError("init(coder:) has not been implemented")
  }

  private func setupView() {
    videoView.videoContentMode = .scaleAspectFill // 设置填充模式,适配千萬级 App UI
    videoView.translatesAutoresizingMaskIntoConstraints = false
    self.addSubview(videoView)
    NSLayoutConstraint.activate([
      videoView.leadingAnchor.constraint(equalTo: self.leadingAnchor),
      videoView.trailingAnchor.constraint(equalTo: self.trailingAnchor),
      videoView.topAnchor.constraint(equalTo: self.topAnchor),
      videoView.bottomAnchor.constraint(equalTo: self.bottomAnchor)
    ])
    
    // 千万级关键点:将该渲染视图注册给单例
    VideoRenderRegistry.shared.registerRemoteView(view: self.videoView)
  }
  
  // 如果需要传递事件给 RN
  @objc var onRemoteStreamAvailable: RCTDirectEventBlock?
}

// 简单的 Swift 单例,用于在原生层传递数据流
class VideoRenderRegistry {
  static let shared = VideoRenderRegistry()
  private var remoteViewRef: RTCMTLVideoView?
  private var remoteVideoTrackRef: RTCVideoTrack?

  private init() {} // 阻止外部初始化

  func registerRemoteView(view: RTCMTLVideoView) {
    self.remoteViewRef = view
    // 如果流已经准备好,直接绑定
    self.remoteVideoTrackRef?.add(view)
  }

  // 这个方法由 AVNativeModuleDelegate 在接收到远程流时调用
  func onRemoteStreamReady(videoTrack: RTCVideoTrack) {
    self.remoteVideoTrackRef = videoTrack
    self.remoteViewRef?.let {
      videoTrack.add(it)
    }
  }
}

// Manager:负责向 RN 注册和创建这个自定义视图组件
@objc(RTCViewNativeManager)
class RTCViewNativeManager: RCTViewManager {
  override func view() -> UIView! {
    return RTCViewNative()
  }
  
  override class func requiresMainQueueSetup() -> Bool {
    return true // UIKit 组件必须在主线程初始化
  }
}

2.4.3 iOS 模块桥接文件 (AVNativeModule.m / RTCViewNativeManager.m)

在 iOS 中,即使使用 Swift,也必须编写 Objective-C 桥接文件来将这些方法暴露出给 RN 的 JS 引擎。

// iOS/AVNativeModule.m - 暴露方法和事件

#import <React/RCTBridgeModule.h>
#import <React/RCTEventEmitter.h>

@interface RCT_EXTERN_MODULE(AVNativeModule, RCTEventEmitter) // Swift 类和基类

RCT_EXTERN_METHOD(startCall:(NSString *)deviceId resolver:(RCTPromiseResolveBlock)resolve rejecter:(RCTPromiseRejectBlock)reject)
RCT_EXTERN_METHOD(stopCall)
RCT_EXTERN_METHOD(muteMicrophone:(BOOL)isMuted)

@end

// iOS/RTCViewNativeManager.m - 暴露自定义组件

#import <React/RCTViewManager.h>

@interface RCT_EXTERN_MODULE(RTCViewNativeManager, RCTViewManager) // Swift Manager 类

RCT_EXPORT_VIEW_PROPERTY(onRemoteStreamAvailable, RCTDirectEventBlock) // 如果需要暴露属性或事件给 RN 组件

@end


第三部分:总结与建议

这份技术方案和代码剖析提供了一个坚实、成熟的路径,用于通过单一 React Native 代码库,构建同时满足 Android 和 iOS 千万级用户体验的视频物联网 App。

3.1 方案核心总结:

  1. 架构:RN 处理 UI 与业务逻辑,原生核心模块处理高性能、不稳定的音视频和 IoT 协议栈。
  2. 音视频:必须使用WebRTC 的原生版本 (Kotlin/Swift),严禁使用 JS WebRTC 库。视频渲染必须使用硬件加速的自定义 View (如 Metal 的 RTCMTLVideoView for iOS, 使用 SurfaceViewRenderer for Android)。
  3. 连接策略:在 Android 和 iOS 原生层实现 Offer/Answer 和 ICE Candidate 的交换(配合云端信令),并通过单例模式将接收到的原生流对象 (VideoTrack) 直接绑定到桥接给 RN 的原生 View 上。

3.2 对构建千万级 App 的进一步建议:

  • 信令服务器与 STUN/TURN:千万级 App 的成功关键在于连接建立成功率和稳定性。你需要自行部署并优化高可用的 STUN/TURN 服务器阵列(或使用 AWS Kinesis Video Streams 或阿里云音视频服务),并确保信令服务器是可横向扩展的。
  • A/B 测试:对于核心功能,如“秒开时间”、“视频延时”,应使用 A/B 测试来持续优化性能。
  • 端到端监控:构建完善的视频监控系统。监控 WebRTC 的丢包率 (Packet Loss), 帧率 (FPS), 连接时间 (Setup Time), 以及用户端的网络状态,以便快速定位和解决个别设备或网络环境的体验问题。

希望这个深度方案和 Demo 剖析能成为你构建世界级 React Native 视频物联网 App 的一个极具价值的参考指南!

Logo

开源鸿蒙跨平台开发社区汇聚开发者与厂商,共建“一次开发,多端部署”的开源生态,致力于降低跨端开发门槛,推动万物智联创新。

更多推荐