跳到主要内容

ReactNative与Flutter

React Native 与 Flutter

跨端 App 框架里 RN 和 Flutter 是绕不开的两家。WebRTC 在这两边都有"事实标准"的开源库:react-native-webrtcflutter_webrtc,都基于各平台原生 libwebrtc 做桥接。本篇讲集成、性能、版本同步、踩坑。

1. 选型对比

维度react-native-webrtcflutter_webrtc
维护方react-native-webrtc 社区(核心成员含 Jitsi)flutter-webrtc 社区(CloudWebRTC 维护)
底层iOS 用官方 WebRTC.xcframework,Android 用 aar同左
API 风格几乎 1:1 映射浏览器 WebRTC同左 + Dart 风格封装
TS / Dart 类型有官方 d.ts完整 Dart 类型
Web 端走原生 WebRTC(RN Web 部分场景)flutter_webrtc Web 走 dart:html
视频渲染RTCView(基于原生 View)RTCVideoView(PlatformView)
生态成熟度高,Jitsi 在用高,OpenIMSDK / Janus dart-client 在用

什么时候适用:业务团队已选定 RN 或 Flutter 作为 App 技术栈;要直接复用前端的 WebRTC 心智模型;不打算自己维护 libwebrtc 编译。 什么时候不适用:需要在采集前做 GPU pipeline 美颜并且与现有原生美颜框架深度集成;要做厂商私有协议(这种通常厂商有自己的 RN/Flutter 包装)。

2. 版本同步问题(再次强调)

两个库都在 README 写明对应的 libwebrtc milestone:

库版本iOS libwebrtcAndroid libwebrtc
react-native-webrtc 124.xM124M124
react-native-webrtc 118.xM118M118
flutter_webrtc 0.10.xM122M122
flutter_webrtc 0.11.xM125M125

如果业务里同时有 H5、RN、Flutter、原生四端,需要把它们都对齐到同一个 milestone(±1 个版本)。否则会出现:H5 (Chrome M125) ↔ RN (M118) 的 simulcast SSRC 不一致 / a=rid 解析差异等问题。

操作要点

  • 每季度同步一次。
  • 升级前在测试房间跑互通矩阵(每端互拉)。
  • 把 milestone 写进 release notes。

3. react-native-webrtc 集成

3.1 安装

npm install react-native-webrtc
cd ios && pod install

3.2 iOS 配置

ios/Podfile

platform :ios, '13.0'
use_modular_headers!

Info.plist

<key>NSCameraUsageDescription</key><string>视频通话</string>
<key>NSMicrophoneUsageDescription</key><string>语音通话</string>
<key>UIBackgroundModes</key>
<array><string>audio</string><string>voip</string></array>

iOS 13 是 react-native-webrtc 124 的最低要求。如果业务必须支持 iOS 12,要回退到老版本(带的 libwebrtc 也老)。

3.3 Android 配置

android/app/src/main/AndroidManifest.xml

<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.CAMERA" />
<uses-permission android:name="android.permission.RECORD_AUDIO" />
<uses-permission android:name="android.permission.MODIFY_AUDIO_SETTINGS" />
<uses-permission android:name="android.permission.BLUETOOTH" />
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
<uses-permission android:name="android.permission.FOREGROUND_SERVICE_MICROPHONE" />
<uses-permission android:name="android.permission.FOREGROUND_SERVICE_CAMERA" />

android/app/build.gradle

android {
compileSdkVersion 34
defaultConfig {
minSdkVersion 24 // 24 是 react-native-webrtc 124 的底线
targetSdkVersion 34
}
packagingOptions {
pickFirst '**/libc++_shared.so'
pickFirst '**/libjsc.so'
}
}

3.4 TypeScript 用法

import {
RTCPeerConnection,
RTCIceCandidate,
RTCSessionDescription,
mediaDevices,
RTCView,
MediaStream,
} from 'react-native-webrtc';

async function startCall(signaling: Signaling) {
const local = await mediaDevices.getUserMedia({
audio: true,
video: {
facingMode: 'user',
width: 1280,
height: 720,
frameRate: 30,
},
}) as MediaStream;

const pc = new RTCPeerConnection({
iceServers: [{ urls: 'stun:stun.l.google.com:19302' }],
bundlePolicy: 'max-bundle',
});

local.getTracks().forEach(t => pc.addTrack(t, local));

pc.addEventListener('icecandidate', (e) => {
if (e.candidate) signaling.send({ type: 'candidate', candidate: e.candidate });
});

const offer = await pc.createOffer({});
await pc.setLocalDescription(offer);
signaling.send({ type: 'offer', sdp: offer.sdp });
return { pc, local };
}

3.5 视频渲染

import { RTCView } from 'react-native-webrtc';

<RTCView
streamURL={localStream.toURL()}
style={{ width: 240, height: 320 }}
objectFit="cover"
mirror={true}
zOrder={1}
/>

streamURL 是个 hack:拿到的不是 URL,是注册过的 stream id。改了 stream 的 track 后必须重新 toURL() 拿新值

3.6 InCallManager

通话路由(听筒/扬声器/蓝牙)官方没封装,要配合:

npm install react-native-incall-manager
import InCallManager from 'react-native-incall-manager';

InCallManager.start({ media: 'audio/video' });
InCallManager.setForceSpeakerphoneOn(true);
// 结束
InCallManager.stop();

4. flutter_webrtc 集成

4.1 安装

# pubspec.yaml
dependencies:
flutter_webrtc: ^0.11.0
flutter pub get
cd ios && pod install

4.2 iOS / Android 配置

与 RN 完全一致(Info.plist / AndroidManifest)。Flutter 还要在 ios/Podfileplatform :ios, '13.0' 打开,并 use_frameworks!

4.3 Dart 用法

import 'package:flutter_webrtc/flutter_webrtc.dart';

class RtcEngine {
RTCPeerConnection? _pc;
MediaStream? _local;

Future<void> start(Signaling sig) async {
_local = await navigator.mediaDevices.getUserMedia({
'audio': true,
'video': {
'facingMode': 'user',
'width': 1280,
'height': 720,
'frameRate': 30,
},
});

_pc = await createPeerConnection({
'iceServers': [
{'urls': 'stun:stun.l.google.com:19302'}
],
'sdpSemantics': 'unified-plan',
'bundlePolicy': 'max-bundle',
});

_local!.getTracks().forEach((t) {
_pc!.addTrack(t, _local!);
});

_pc!.onIceCandidate = (c) => sig.send({'type': 'candidate', 'candidate': c.toMap()});

final offer = await _pc!.createOffer({});
await _pc!.setLocalDescription(offer);
sig.send({'type': 'offer', 'sdp': offer.sdp});
}
}

4.4 视频渲染

final RTCVideoRenderer _renderer = RTCVideoRenderer();
await _renderer.initialize();
_renderer.srcObject = _local;

// 在 widget tree
RTCVideoView(
_renderer,
objectFit: RTCVideoViewObjectFit.RTCVideoViewObjectFitCover,
mirror: true,
)

RTCVideoRenderer 必须 initialize 之后才能赋 srcObject,而且 dispose() 之后不能再用,得新建。

5. 性能与渲染

维度RNFlutter
视频帧路径原生 View 直接渲染(不过 JS bridge)PlatformView,纹理共享
多路视频同屏(9 宫格)60 fps 没问题60 fps 没问题
远端订阅切换修改 streamURL 流畅替换 srcObject 流畅
JS / Dart 侧 CPU 占用1~3%1~3%
自定义采集(美颜)RN 老社区方案少,需要原生模块Flutter 有 getDisplayMedia 桥,美颜需原生
桌面端RN-Windows / RN-macOS 实验Flutter 桌面成熟

整体性能瓶颈在底层 libwebrtc(编解码、网络),RN/Flutter 这一层不显著影响实时性。

6. RN ↔ Flutter ↔ 浏览器互通

互通能跑通的前提:

  1. 三端 libwebrtc milestone 接近。
  2. 信令服务格式统一(SDP 字符串 + ICE candidate)。
  3. Codec 在三端交集里——H.264 永远 OK,VP8 在大多数版本也 OK,VP9 在 Safari 16+ 才行。
  4. Simulcast 字段 a=rid 三端实现一致;若不一致退化为单层。

实战建议:先用 1v1 SDP 跑通,再开 Simulcast;不要一上来就堆 SVC。

7. 失败时的样子

现象大概原因处理
iOS 上 pod install 报 use_frameworks 冲突与 Firebase / 其他 pod 冲突use_frameworks! :linkage => :static
Android 启动 NDK 冲突多个 native lib 都带 libc++_shared.sopickFirst
RTCView 切流后画面不更新streamURL 没重新 toURL改 state 触发 re-render
Flutter RTCVideoView 黑屏_renderer.initialize() 未 await加 await
Android 14 通话进程被杀没启 FGS启 microphone / camera 类型 FGS
iOS 后台 30s 断没勾 audio background modeInfo.plist 补 UIBackgroundModes
远端订阅大房间 OOMiOS / 低端 Android限制订阅路数 + simulcast 拉低层
蓝牙耳机切换无声路由没切RN 用 InCallManager,Flutter 用 Helper.selectAudioOutput

8. 调试技巧

8.1 RN

import { registerGlobals } from 'react-native-webrtc';
registerGlobals(); // 让 window.RTCPeerConnection 也指向 RN 实现

// 拿 stats
const stats = await pc.getStats(null);
stats.forEach(r => {
if (r.type === 'inbound-rtp' && r.kind === 'video') {
console.log(r.framesPerSecond, r.bytesReceived);
}
});

8.2 Flutter

final stats = await _pc!.getStats();
for (final r in stats) {
if (r.type == 'inbound-rtp' && r.values['kind'] == 'video') {
print(r.values);
}
}

两个库都能拿到完整 RTCStatsReport,监控/质量评估代码可以和 H5 共用结构。

9. 反模式

反模式后果
RN 与 H5 / 原生用不同 milestone互通时 simulcast / rid 解析 bug
RN 用 Bridge 转 base64 传视频帧帧率/CPU 灾难,应走原生 View
Flutter 用 Image widget 显示视频帧不连续,必须用 RTCVideoView
不 dispose RTCVideoRenderer内存泄漏,多次进房后崩
自己写 SDP 字符串拼接做 codec 过滤维护成本极高,应用 setCodecPreferences
Android 不配 ProGuard keeprelease 包 crash 在反射
直接调浏览器 setSinkIdRN / Flutter 都不支持,要用 native helper
在 RN JS 线程做大量帧处理卡 UI,应通过原生模块走 GPU

10. 与 Jitsi / LiveKit / TRTC SDK 的关系

  • Jitsi Meet SDK (RN):基于 react-native-webrtc 二次封装的会议 SDK,方便快速接入完整会议室。代价是定制空间小,业务 UI 必须用它的 React Component。
  • LiveKit Client SDK (RN/Flutter):基于 react-native-webrtc / flutter_webrtc 封装 LiveKit 协议(SFU)。开源、可自部署。
  • 腾讯 TRTC RN / Flutter SDK:腾讯官方包,底层不是 react-native-webrtc,而是 TRTC 原生 SDK 桥接,互通封闭在 TRTC 体系内。
  • 声网 Agora RN / Flutter SDK:同 TRTC,独立体系。

选择维度

场景建议
自有 SFU + 标准 WebRTCreact-native-webrtc / flutter_webrtc
LiveKit 部署LiveKit SDK
Jitsi 部署Jitsi Meet SDK
与小程序无缝互通TRTC / 声网 各自 RN/Flutter 包
全球节点 + 一站式TRTC / 声网

11. 权威资料