# Flutter WebRTC 入门实战:5 步实现本地视频预览,搞懂 getUserMedia、RTCVideoRenderer、RTCVideoView
Flutter WebRTC 入门实战:5 步实现本地视频预览,搞懂 getUserMedia、RTCVideoRenderer、RTCVideoView
这篇文章先不讲 Socket、Offer、Answer、Candidate,也不讨论复杂的音视频架构。
我们先做一件最重要的事:
确认 flutter_webrtc 已经可以在 Flutter 项目里采集并渲染本地视频。
对初学者来说,这是最值得先完成的一步。因为只有本地采集和本地渲染先跑通,后面的信令、房间、通话流程才有意义。
一、先看最重要的结论
如果你只是想先知道“本地预览到底怎么跑起来”,可以先记住这一条链:
- 创建
RTCVideoRenderer - 调用
initialize() - 调用
navigator.mediaDevices.getUserMedia(...) - 得到本地
MediaStream - 执行
renderer.srcObject = stream - 用
RTCVideoView(renderer)把画面显示出来
也就是说,最核心的顺序其实就是:
先初始化渲染器 -> 再获取本地媒体流 -> 再把流绑定给渲染器 -> 最后交给 RTCVideoView 显示
这一篇文章就围绕这几个 RTC 相关类、调用参数和调用步骤来讲,不展开页面架构本身。
二、flutter_webrtc 是什么
flutter_webrtc 是一个 Flutter 插件,用来在 Flutter 中接入 WebRTC 能力。根据它的官方 Pub 页面描述,它是一个基于 Google WebRTC 的 Flutter 插件,支持 iOS、Android、Desktop 和 Web。
官方资料:
flutter_webrtcPub 页面:https://pub.dev/packages/flutter_webrtc- Flutter WebRTC Community Docs:https://flutter-webrtc.org/docs/docs/
- WebRTC 官方概览:https://webrtc.org/getting-started/overview
简单理解,它帮我们在 Flutter 里封装了这些核心能力:
- 采集摄像头和麦克风
- 创建本地媒体流
MediaStream - 创建
RTCPeerConnection - 交换 Offer / Answer / ICE Candidate
- 渲染本地和远端视频
如果没有这个插件,我们就需要分别去处理 iOS、Android、Web 的原生 WebRTC 接入细节,成本会高很多。
三、它主要是做什么的
flutter_webrtc 最常见的用途有 4 类:
- 音视频通话
- 直播连麦
- 屏幕共享
- DataChannel 实时数据通信
对于一个聊天项目来说,最典型的使用方式通常是:
- Flutter 页面负责 UI 展示
flutter_webrtc负责媒体采集、连接建立和视频渲染- Socket 负责传递信令消息
不过在真正进入“通话”之前,我们完全可以先只做本地预览。
四、为什么第一步先做“本地预览”
可以,而且非常建议先这样做。
原因很简单:
- 可以先验证摄像头权限有没有问题
- 可以先验证
RTCVideoRenderer和RTCVideoView是否工作正常 - 可以先验证真机环境是否已经满足
flutter_webrtc的运行要求 - 出问题时,排查范围会小很多
当前项目里虽然用了一个预览页面承载这条链路,但真正值得初学者关注的,不是页面结构,而是下面这些 RTC 相关对象和调用。
五、这一篇里最值得先认识的 RTC 类
1. RTCVideoRenderer
它是视频渲染器。
你可以把它理解成:
- Dart 层持有的一个视频输出对象
- 后面会和原生视频帧渲染链路连接起来
它本身不负责采集视频,它负责:
- 接收某条媒体流
- 把这条流交给底层渲染链路
- 最后让
RTCVideoView有内容可显示
使用前通常要先初始化:
final RTCVideoRenderer renderer = RTCVideoRenderer();
await renderer.initialize();
2. MediaStream
它表示一条媒体流。
一条 MediaStream 里通常包含:
- 音频轨
- 视频轨
本地预览场景下,它通常来自:
- 摄像头
- 麦克风
后面我们会通过 getUserMedia(...) 去拿到它。
3. MediaStreamTrack
如果说 MediaStream 是“一整条流”,那 MediaStreamTrack 就是这条流里面的具体轨道。
常见的就是两种:
- 音频轨
audio track - 视频轨
video track
后面开关麦克风、开关摄像头,本质上操作的都是 track.enabled。
4. RTCVideoView
它是 Flutter Widget,用来把 RTCVideoRenderer 里的画面显示到页面上。
例如:
RTCVideoView(
renderer,
mirror: true,
objectFit: RTCVideoViewObjectFit.RTCVideoViewObjectFitCover,
)
所以它的职责很明确:
RTCVideoRenderer负责持有视频输出RTCVideoView负责把这个输出显示到 Flutter 页面
5. RTCPeerConnection
这是 WebRTC 建立点对点连接的核心对象。
不过第一篇文章里可以先不展开它,因为本地预览不需要建立 RTCPeerConnection。
第一篇的目标是先把自己的视频显示出来。
六、最关键的调用:getUserMedia(...)
根据 Flutter WebRTC Community Docs,getUserMedia(...) 的作用是:
向设备申请本地媒体采集,并返回一个 MediaStream。
官方文档:https://flutter-webrtc.org/docs/flutter-webrtc/api-docs/get-user-media/
当前项目中的调用是:
final MediaStream stream = await navigator.mediaDevices.getUserMedia(<String, dynamic>{
'audio': true,
'video': <String, dynamic>{
'facingMode': 'user',
'width': <String, dynamic>{'ideal': 1280},
'height': <String, dynamic>{'ideal': 720},
},
});
这段代码的作用就是:
- 打开麦克风
- 打开摄像头
- 返回一条本地音视频流
MediaStream
参数逐项解释
1. 'audio': true
表示:
需要音频输入
也就是要打开麦克风。
2. 'video': {...}
表示:
需要视频输入
也就是要打开摄像头。
3. 'facingMode': 'user'
表示:
优先使用前置摄像头
移动端常见值有两个:
'user':前置摄像头'environment':后置摄像头
所以本地预览场景里,默认用 'user' 很合理。
4. 'width': {'ideal': 1280}
表示:
期望视频宽度尽量接近 1280
这里用的是 ideal,意思不是“必须是 1280”,而是“尽量满足”。
5. 'height': {'ideal': 720}
表示:
期望视频高度尽量接近 720
所以这一组参数的目标,基本可以理解成:
- 希望拿到接近
1280 x 720的本地视频流 - 也就是接近 720p
为什么这里更推荐 ideal
因为入门场景下,兼容性往往比“必须固定分辨率”更重要。
如果设备不支持某个强约束值,直接失败会让初学者更难排查。
而 ideal 的语义更温和:
- 能满足最好
- 不能完全满足,也尽量给你接近的结果
七、拿到 MediaStream 之后,下一步要做什么
拿到本地流以后,还不能立刻显示。
你还需要做这一步:
renderer.srcObject = stream;
这一步的作用是:
把刚拿到的本地媒体流交给渲染器。
如果没有这一步:
- 你虽然成功拿到了
MediaStream - 但
RTCVideoRenderer还不知道该显示哪条流
所以它的本质是:
把“采集结果”交给“显示管线”。
八、最后是谁把视频显示到页面上的
真正把视频显示出来的是:
RTCVideoView(
renderer,
mirror: true,
objectFit: RTCVideoViewObjectFit.RTCVideoViewObjectFitCover,
)
这里几个参数值得初学者先记住:
1. renderer
表示:
当前要显示哪个 RTCVideoRenderer 里的画面
2. mirror: true
表示:
镜像显示
本地预览场景里通常会打开镜像,这样看起来更符合自拍习惯。
3. objectFit: RTCVideoViewObjectFit.RTCVideoViewObjectFitCover
表示:
画面尽量铺满当前区域
它更像 Flutter 里图片或视频的 cover 效果。
九、控制摄像头和麦克风,本质上是在控制什么
很多初学者看到“静音”“关摄像头”时,会以为又重新调用了一次采集接口。
其实不是。
当前项目里的做法是:
- 麦克风开关:遍历
stream.getAudioTracks() - 摄像头开关:遍历
stream.getVideoTracks() - 然后修改
track.enabled
也就是说:
getUserMedia(...)是“获取流”track.enabled = false是“关闭已有轨道输出”
这是两个不同层面的动作。
麦克风开关
for (final MediaStreamTrack track in stream.getAudioTracks()) {
track.enabled = nextEnabled;
}
作用:
- 开启或关闭音频轨
摄像头开关
for (final MediaStreamTrack track in stream.getVideoTracks()) {
track.enabled = nextEnabled;
}
作用:
- 开启或关闭视频轨
十、切换前后摄像头是怎么做的
当前项目里使用的是 flutter_webrtc 提供的辅助方法:
await Helper.switchCamera(stream.getVideoTracks().first, null, stream);
这句话的作用是:
- 取出当前视频轨
- 切换它背后的摄像头设备
所以这里不是重新从零写一套摄像头切换逻辑,而是直接使用插件能力。
十一、为什么还要关心资源释放
本地预览不是“显示一下图片”那么简单,它背后持有的是:
- 摄像头资源
- 麦克风资源
- 本地媒体流对象
- 视频渲染器对象
所以用完以后要做清理。
当前项目里主要有两类清理动作:
1. 释放本地流
关键动作包括:
renderer.srcObject = nulltrack.stop()stream.dispose()
这一步的目的是:
- 停止当前采集
- 释放摄像头和麦克风占用
2. 释放渲染器
await renderer.dispose();
这一步的目的是:
- 释放渲染器相关资源
如果不做这些清理,后续页面切换、重复打开预览、甚至通话流程都可能出现问题。
十二、把整个调用顺序再串一遍
这一篇文章最核心的调用步骤,总结起来就是:
第一步:创建渲染器
final RTCVideoRenderer renderer = RTCVideoRenderer();
作用:
- 创建一个本地视频输出对象
第二步:初始化渲染器
await renderer.initialize();
作用:
- 为后续视频显示做好准备
第三步:申请本地媒体流
final MediaStream stream = await navigator.mediaDevices.getUserMedia({
'audio': true,
'video': {
'facingMode': 'user',
'width': {'ideal': 1280},
'height': {'ideal': 720},
},
});
作用:
- 打开麦克风
- 打开摄像头
- 返回本地
MediaStream
第四步:把流绑定给渲染器
renderer.srcObject = stream;
作用:
- 告诉渲染器要显示这条本地流
第五步:把渲染器交给 RTCVideoView
RTCVideoView(
renderer,
mirror: true,
objectFit: RTCVideoViewObjectFit.RTCVideoViewObjectFitCover,
)
作用:
- 在 Flutter 页面上真正把本地视频显示出来
十三、flutter_webrtc 的优点
结合官方文档和实际项目经验,flutter_webrtc 的优点主要有这些。
1. 跨平台能力比较完整
从 Pub 页面可以看到,它支持:
- iOS
- Android
- Web
- macOS
- Windows
- Linux
这对 Flutter 项目非常友好。
2. 核心能力覆盖比较全
官方列出的能力包括:
- Audio / Video
- Data Channel
- Screen Capture
- Unified Plan
- Simulcast
所以不只是简单视频预览,后续做实时音视频、直播连麦、屏幕共享,也有继续扩展的空间。
3. 和 Flutter 页面结合自然
Flutter 侧直接通过 RTCVideoView 展示视频,页面层写法相对统一。
对 Flutter 开发者来说,上手门槛比自己封装原生插件低很多。
4. 社区资料相对集中
至少入门阶段,下面几类资料是比较容易找到的:
- Pub 文档
- 官方社区文档
- 示例项目
- 常见问题讨论
十四、flutter_webrtc 的局限和注意点
初学者也要知道,它并不是“装上就万事大吉”的插件。
1. 需要理解一些 WebRTC 基础概念
即便插件已经帮你封装了很多能力,你仍然需要逐步理解:
MediaStreamMediaStreamTrackRTCPeerConnection- Offer / Answer
- ICE Candidate
- STUN / TURN
否则后面做通话时很容易只会“照着抄代码”,但不理解为什么这样写。
2. 真机调试非常重要
尤其是摄像头、麦克风、权限、前后摄切换这类能力,模拟器环境通常不够完整。
所以真正做视频能力时,建议尽量用真机验证。
3. 平台差异不能完全忽略
虽然 flutter_webrtc 对外暴露的是统一接口,但底层依然涉及:
- iOS 原生 WebRTC
- Android 原生 WebRTC
- 各平台权限配置
- 渲染实现差异
所以复杂场景下,还是要具备一点平台层认知。
4. 一旦进入通话实战,信令设计会变复杂
视频通话真正难的地方,很多时候不在“把画面显示出来”,而在:
- 如何设计信令
- 如何处理状态同步
- 如何管理 roomId / sessionId
- 如何处理拒接、忙线、超时、挂断
这些内容更适合放到第二篇文章单独讲。
十五、小结
这篇文章最重要的结论有 4 个:
flutter_webrtc是 Flutter 接入 WebRTC 的核心插件- 对初学者来说,第一步最应该先做“本地预览”
- 本地预览最核心的 RTC 类是:
RTCVideoRenderer、MediaStream、MediaStreamTrack、RTCVideoView - 当前这条预览链路本质上就是:初始化渲染器 -> 获取本地流 -> 绑定渲染器 -> 页面显示
如果你正在跟着当前项目一起学,建议先验证下面这几件事:
- 能否正常申请摄像头和麦克风权限
- 能否看到自己的本地画面
- 能否切换前后摄像头
- 能否控制麦克风和摄像头开关
这些都跑通以后,再进入第二篇去讲:
- 聊天场景下如何通过 Socket 承载 WebRTC 信令
callInvite / callAccept / webrtcOffer / webrtcAnswer / webrtcIceCandidate是如何协作的
参考资料
flutter_webrtcPub 页面:https://pub.dev/packages/flutter_webrtc- Flutter WebRTC Community Docs:https://flutter-webrtc.org/docs/docs/
- Flutter WebRTC
getUserMedia文档:https://flutter-webrtc.org/docs/flutter-webrtc/api-docs/get-user-media/ - WebRTC 官方概览:https://webrtc.org/getting-started/overview
作者: 911hzh
邮箱: 911hzh@gmail.com
需要demo:请私信
火山引擎视频云技术社区,是面向 AI 音视频开发者的技术交流平台。这里汇聚源自抖音、豆包等亿级 DAU 产品的 RTC、直播、点播、AI 媒体处理、音视频互动技术,提供接入指南、最佳实践、性能调优、场景案例、Demo 代码、开源项目、白皮书和 API 文档。社区汇聚官方工程师与一线开发者,为 AI 视频通话、数字人、AI 视频处理等应用的开发与落地提供技术支持。
更多推荐
所有评论(0)