21、移动端WebRTC:React Native与WebRTC、Android原生WebRTC集成、iOS原生WebRTC集成
移动端做WebRTC,说实话,坑比PC端多不少。
我最早在React Native上集成WebRTC时,以为就是装个npm包的事。结果一跑起来,视频黑屏、音频没声、连接断断续续……折腾了整整一周。后来我才明白,移动端WebRTC不是“装个库”就完事的,你得理解底层怎么跑,才能用好它。
这一章,咱们就聊聊三个方向:React Native、Android原生、iOS原生。我会把我在项目中踩过的坑、总结的经验,都摊开来讲。
核心要点:移动端WebRTC的难点不在API调用,而在权限管理、生命周期、硬件适配和原生桥接。这三个平台各有各的脾气,但底层逻辑是相通的。
21.1 React Native + WebRTC:跨平台的最优解?
React Native做WebRTC,说白了就是“一套代码,两端跑”。但你要知道,WebRTC本身是C++写的,RN只是给你包了一层JS桥。所以性能瓶颈不在JS,而在桥接层。
我个人习惯用 react-native-webrtc 这个库。它封装了大部分原生逻辑,但有些细节你得自己处理。
安装与基础配置
npm install react-native-webrtc
cd ios && pod install
嗯,这里要注意:Android端不需要额外配置,但iOS端必须在 Info.plist 里添加摄像头和麦克风权限描述。不然一调用就闪退,我当初就栽在这上面。
| 平台 | 所需权限 | 配置位置 |
|---|---|---|
| iOS | NSCameraUsageDescription, NSMicrophoneUsageDescription | Info.plist |
| Android | CAMERA, RECORD_AUDIO | AndroidManifest.xml |
核心代码示例
import { RTCPeerConnection, mediaDevices } from 'react-native-webrtc';
const pc = new RTCPeerConnection({
iceServers: [{ urls: 'stun:stun.l.google.com:19302' }]
});
// 获取本地流
const stream = await mediaDevices.getUserMedia({
audio: true,
video: { facingMode: 'user' }
});
stream.getTracks().forEach(track => pc.addTrack(track, stream));
// 创建offer
const offer = await pc.createOffer();
await pc.setLocalDescription(offer);
这段代码看着简单,但有个坑:RN的媒体流生命周期和组件生命周期是分离的。如果你在组件卸载时没有释放流,摄像头会一直亮着。我曾经有个用户反馈说“为什么我的手机发烫?”——就是因为流没释放。
避坑指南:在 useEffect 的清理函数中,一定要调用 stream.getTracks().forEach(track => track.stop())。同时把 pc.close() 也加上。
21.2 Android原生集成:从JNI到SurfaceView
Android原生集成WebRTC,我建议直接用Google官方的 org.webrtc:google-webrtc 库。它封装了底层JNI调用,你只需要关注Java层的逻辑。
为什么会这样?因为WebRTC的C++层在Android上是通过JNI桥接的,如果你自己写JNI,那工作量太大了。官方库已经帮你做好了。
Gradle依赖
implementation 'org.webrtc:google-webrtc:1.0.32006'
核心流程
// 初始化PeerConnectionFactory
PeerConnectionFactory.InitializationOptions initOptions =
PeerConnectionFactory.InitializationOptions.builder(context)
.setFieldTrials("")
.createInitializationOptions();
PeerConnectionFactory.initialize(initOptions);
PeerConnectionFactory factory = PeerConnectionFactory.builder()
.setVideoDecoderFactory(new DefaultVideoDecoderFactory(eglBaseContext))
.setVideoEncoderFactory(new DefaultVideoEncoderFactory(eglBaseContext, true, true))
.createPeerConnectionFactory();
// 创建PeerConnection
PeerConnection.RTCConfiguration config = new PeerConnection.RTCConfiguration(iceServers);
PeerConnection pc = factory.createPeerConnection(config, observer);
这里有个关键点:EGLContext。Android的硬件编码解码依赖OpenGL ES,你必须先创建一个EGL上下文。我刚开始没注意,结果视频一直黑屏,查了两天才发现是EGL没初始化。
警告:Android 12以上,后台应用无法访问摄像头。如果你的App在后台时还想保持视频通话,必须使用前台服务(Foreground Service)。否则系统会直接杀掉你的摄像头。
SurfaceView渲染
Android端渲染视频,我推荐用 SurfaceViewRenderer。它比TextureView性能更好,尤其是在低端机上。
SurfaceViewRenderer localRenderer = findViewById(R.id.local_renderer);
localRenderer.init(eglBaseContext, null);
VideoSource videoSource = factory.createVideoSource(false);
VideoTrack localVideoTrack = factory.createVideoTrack("local_track", videoSource);
// 将摄像头采集绑定到VideoSource
CameraVideoCapturer capturer = Camera2Enumerator.createCapturer();
capturer.initialize(surfaceTextureHelper, context, videoSource.getCapturerObserver());
capturer.startCapture(1280, 720, 30);
嗯,这里要注意:startCapture 的分辨率不要设太高。720p在大多数手机上够用了,1080p反而会增加编码延迟。我测试过,720p下端到端延迟能控制在200ms以内,1080p就飙到400ms了。
21.3 iOS原生集成:Swift与Objective-C的抉择
iOS端集成WebRTC,我建议用CocoaPods引入 GoogleWebRTC 库。它包含了编译好的framework,省去了自己编译的麻烦。
我个人习惯用Swift写新项目,但WebRTC的iOS API是Objective-C风格的。所以你得在Swift里做桥接,或者直接用OC写。
Podfile配置
pod 'GoogleWebRTC'
Swift集成示例
import GoogleWebRTC
// 初始化工厂
let factory = RTCPeerConnectionFactory()
// 配置ICE
let config = RTCConfiguration()
config.iceServers = [RTCIceServer(urlStrings: ["stun:stun.l.google.com:19302"])]
// 创建连接
let pc = factory.peerConnection(with: config, constraints: constraints, delegate: self)
// 获取本地流
let mediaConstraints = RTCMediaConstraints(mandatoryConstraints: nil, optionalConstraints: nil)
let audioSource = factory.audioSource(with: mediaConstraints)
let audioTrack = factory.audioTrack(with: audioSource, trackId: "audio0")
let videoSource = factory.videoSource()
let videoTrack = factory.videoTrack(with: videoSource, trackId: "video0")
// 绑定摄像头
let capturer = RTCCameraVideoCapturer(delegate: videoSource)
if let device = RTCCameraVideoCapturer.captureDevices().first {
try? capturer.startCapture(with: device,
format: device.supportedFormats.last!,
fps: 30)
}
pc.add(audioTrack, streamIds: ["stream0"])
pc.add(videoTrack, streamIds: ["stream0"])
这段代码看着挺长,但核心逻辑就三步:建工厂、配连接、绑设备。我刚开始写的时候,总忘记设置 RTCMediaConstraints,结果音频一直没声音。后来才发现,iOS默认不开启音频,你得显式声明。
避坑指南:iOS端如果遇到“摄像头黑屏”,先检查 AVCaptureDevice.authorizationStatus。用户拒绝权限后,WebRTC不会报错,只会返回空流。我曾经因为这个被测试骂了一下午。
21.4 三平台对比与选型建议
你想想看,三个平台各有优劣。我整理了一张对比表,方便你决策:
| 维度 | React Native | Android原生 | iOS原生 |
|---|---|---|---|
| 开发效率 | 高(一套代码) | 中(需写Java/Kotlin) | 中(需写Swift/OC) |
| 性能 | 中(桥接开销) | 高(直接调用JNI) | 高(直接调用C++) |
| 硬件适配 | 依赖原生库 | 完全可控 | 完全可控 |
| 调试难度 | 中(JS层+原生层) | 低(Android Studio) | 低(Xcode) |
| 推荐场景 | 快速原型、中小型项目 | 大型App、定制化需求 | iOS独占、高性能要求 |
我个人建议:如果你团队有前端背景,优先选React Native。但如果你要做音视频通话这种核心功能,还是原生更稳。我在一个项目中用RN做UI层,原生做WebRTC层,通过Native Modules桥接,效果还不错。
21.5 移动端WebRTC的核心架构图
下面这张图,是我根据项目经验总结的移动端WebRTC分层架构。你看一眼,就能明白各层之间的关系:
这张图你看懂了吗?从上到下,应用层只管UI和用户交互,桥接层负责把JS/Java/Swift的调用转成C++,核心层处理信令、连接、媒体流,硬件层管摄像头和麦克风。每一层各司其职,出了问题也好定位。
21.6 实战中的避坑总结
最后,我把我在移动端WebRTC项目中遇到最多的坑,列成清单给你:
- 权限问题:iOS的Info.plist、Android的Manifest,少一个就崩。我建议在App启动时就申请权限,不要等到调用getUserMedia时才弹窗。
- 生命周期:Android的onPause/onResume、iOS的viewDidDisappear,都要对应暂停/恢复摄像头。否则用户切到后台再回来,视频就卡死了。
- ICE失败:移动端网络切换频繁(WiFi切4G),ICE重连机制必须做好。我习惯在onIceConnectionStateChange里监听状态,如果disconnected就主动重启ICE。
- 内存泄漏:PeerConnection、MediaStream、VideoTrack都要在退出时释放。RN里尤其要注意,JS的垃圾回收不会帮你释放原生对象。
- 编解码兼容:Android碎片化严重,有些低端机不支持H264硬件编码。我建议优先用VP8,兼容性最好。
一句话总结:移动端WebRTC,七分在原生,三分在业务。把底层搞扎实了,上层怎么玩都行。