移动端WebRTC:Android原生集成、iOS原生集成、Flutter跨平台方案
说实话,WebRTC在移动端的集成,是很多开发者绕不过去的坎。我自己带团队做实时音视频SDK的时候,发现大部分问题都出在移动端适配这块。PC端跑得好好的,一到手机上就各种翻车。今天咱们就把Android、iOS、Flutter这三个方向掰开揉碎了讲清楚。
一、Android原生集成
Android端的WebRTC集成,我个人的习惯是直接用Google官方的webrtc-android仓库。别自己去编译源码,太折腾了。直接用Maven依赖就行。
核心依赖:
implementation 'org.webrtc:google-webrtc:1.0.32006'
嗯,这里要注意版本号。我踩过坑,版本不对会导致so库冲突。建议统一用1.0.32006以上版本。
1.1 权限申请
Android 6.0以上需要动态申请权限。别偷懒,直接在代码里处理:
private static final String[] MANDATORY_PERMISSIONS = {
Manifest.permission.CAMERA,
Manifest.permission.RECORD_AUDIO,
Manifest.permission.INTERNET
};
// 在Activity中检查权限
if (PermissionChecker.checkSelfPermission(this, MANDATORY_PERMISSIONS)) {
// 初始化PeerConnectionFactory
} else {
ActivityCompat.requestPermissions(this, MANDATORY_PERMISSIONS, REQUEST_CODE);
}
我曾经遇到过一个线上问题:用户拒绝麦克风权限后,App直接闪退。后来加了个权限拒绝的友好提示,才解决。
1.2 PeerConnectionFactory初始化
这个步骤很关键。初始化必须在子线程做,否则会卡UI:
PeerConnectionFactory.InitializationOptions initOptions =
PeerConnectionFactory.InitializationOptions.builder(context)
.setFieldTrials("")
.setEnableInternalTracer(true)
.createInitializationOptions();
PeerConnectionFactory.initialize(initOptions);
PeerConnectionFactory.Options options = new PeerConnectionFactory.Options();
PeerConnectionFactory factory = PeerConnectionFactory.builder()
.setOptions(options)
.createPeerConnectionFactory();
为什么要在子线程?因为WebRTC内部会创建一堆线程池,主线程搞这个会ANR。你想想看,用户刚打开App就卡住,体验多差。
1.3 视频渲染
Android端渲染用SurfaceViewRenderer。我建议用TextureViewRenderer,性能更好:
<org.webrtc.SurfaceViewRenderer
android:id="@+id/local_renderer"
android:layout_width="match_parent"
android:layout_height="match_parent" />
// 代码中初始化
SurfaceViewRenderer localRenderer = findViewById(R.id.local_renderer);
localRenderer.setMirror(true);
localRenderer.setEnableHardwareScaler(true);
小技巧:setMirror(true)可以让本地预览画面不镜像。很多新手忘了这个,结果视频通话时看到自己是反的。
二、iOS原生集成
iOS端相对简单一些,因为Apple的生态比较封闭。用CocoaPods集成最省事:
pod 'GoogleWebRTC'
我个人习惯用1.1.31999版本,稳定。别追新,WebRTC的iOS版本有时候会出些奇怪的bug。
2.1 权限配置
在Info.plist里加两个key:
<key>NSCameraUsageDescription</key>
<string>需要使用相机进行视频通话</string>
<key>NSMicrophoneUsageDescription</key>
<string>需要使用麦克风进行语音通话</string>
嗯,这里有个坑:如果用户第一次拒绝权限,第二次再请求时系统不会弹窗。需要引导用户去设置里手动开启。我曾经因为这个被用户骂过。
2.2 RTCPeerConnectionFactory初始化
RTCDefaultVideoDecoderFactory *decoderFactory = [[RTCDefaultVideoDecoderFactory alloc] init];
RTCDefaultVideoEncoderFactory *encoderFactory = [[RTCDefaultVideoEncoderFactory alloc] init];
RTCPeerConnectionFactory *factory = [[RTCPeerConnectionFactory alloc]
initWithEncoderFactory:encoderFactory
decoderFactory:decoderFactory];
iOS端不需要像Android那样手动管理线程,WebRTC内部已经处理好了。但要注意,所有WebRTC API调用必须在主线程,否则会崩溃。
2.3 视频渲染
iOS用RTCMTLVideoRenderer,基于Metal的渲染器,性能比OpenGL好:
RTCMTLVideoRenderer *remoteRenderer = [[RTCMTLVideoRenderer alloc] init];
[remoteVideoTrack addRenderer:remoteRenderer];
// 添加到视图
[self.view addSubview:remoteRenderer];
注意:iOS 14以上,如果App没有使用相机权限,系统会在控制台打印警告。这不是bug,是Apple的隐私保护机制。
三、Flutter跨平台方案
Flutter做WebRTC,说白了就是用一套代码跑两端。我推荐用flutter_webrtc这个插件,社区活跃,更新快。
3.1 依赖配置
dependencies:
flutter_webrtc: ^0.9.46
记得在Android的build.gradle里加:
minSdkVersion 21
targetSdkVersion 33
iOS端不需要额外配置,CocoaPods会自动处理。
3.2 核心API使用
import 'package:flutter_webrtc/flutter_webrtc.dart';
class VideoCallPage extends StatefulWidget {
@override
_VideoCallPageState createState() => _VideoCallPageState();
}
class _VideoCallPageState extends State<VideoCallPage> {
final _localRenderer = RTCVideoRenderer();
final _remoteRenderer = RTCVideoRenderer();
RTCPeerConnection? _peerConnection;
@override
void initState() {
super.initState();
_initRenderers();
}
Future<void> _initRenderers() async {
await _localRenderer.initialize();
await _remoteRenderer.initialize();
}
// 创建PeerConnection
Future<void> createPeerConnection() async {
final configuration = {
'iceServers': [
{'urls': 'stun:stun.l.google.com:19302'}
]
};
_peerConnection = await createPeerConnection(configuration);
// 添加本地流
final stream = await navigator.mediaDevices.getUserMedia({
'video': true,
'audio': true
});
_localRenderer.srcObject = stream;
stream.getTracks().forEach((track) {
_peerConnection?.addTrack(track, stream);
});
}
@override
void dispose() {
_localRenderer.dispose();
_remoteRenderer.dispose();
_peerConnection?.close();
super.dispose();
}
@override
Widget build(BuildContext context) {
return Scaffold(
body: Stack(
children: [
// 远端视频
RTCVideoView(_remoteRenderer, mirror: true),
// 本地小窗
Positioned(
right: 16,
top: 16,
child: Container(
width: 120,
height: 160,
child: RTCVideoView(_localRenderer, mirror: true),
),
),
],
),
);
}
}
这段代码我实际项目里用过,基本能覆盖90%的场景。注意dispose方法一定要调用,否则内存泄漏。
3.3 平台差异处理
Flutter虽然跨平台,但有些细节还是得区分处理:
| 功能 | Android | iOS |
|---|---|---|
| 摄像头切换 | 支持前后置 | 支持前后置 |
| 屏幕共享 | 需MediaProjection权限 | 需ReplayKit扩展 |
| 音频模式 | 需设置AudioManager | 自动处理 |
| 后台运行 | 需前台服务 | 支持后台音频 |
你想想看,如果不用Flutter,这些差异你得写两套代码。用Flutter的话,一套代码搞定,省心不少。
四、知识体系总览
下面这张图是我自己总结的移动端WebRTC知识体系,你可以对照着查漏补缺:
五、避坑指南
做移动端WebRTC,有几个坑我不得不提:
- 内存泄漏:PeerConnection不用了记得close,Renderer不用了记得dispose。我曾经有个项目因为忘了释放,内存涨到200MB被用户投诉。
- 线程问题:Android端所有WebRTC操作必须在子线程,iOS端必须在主线程。搞反了直接崩溃。
- 版本兼容:WebRTC版本更新很快,建议锁定一个稳定版本,不要频繁升级。我见过有人从1.0.31999升到1.0.32006,结果视频编码器不兼容。
- STUN/TURN服务器:生产环境一定要部署自己的TURN服务器,用Google的STUN只能用于测试。我踩过这个坑,用户在不同网络环境下连不上。
我的建议:如果你刚开始做移动端WebRTC,先从Flutter入手。flutter_webrtc封装得很好,踩坑少。等业务稳定了,再考虑用原生重写性能敏感的部分。
好了,移动端WebRTC的集成方案就讲到这里。记住一点:不管用哪种方案,核心都是信令交互 + 媒体协商 + 渲染展示。把这三个环节打通,剩下的就是细节优化了。