这里以 Flutter 社区常用的 apivideo_live_stream 插件为例,带你从 0 到 1 搭建一个基础的推流页面。
第一步:原生环境权限配置
推流需要调用手机的摄像头和麦克风,因此必须在 Android 和 iOS 的原生配置文件中申请权限。
Android 配置 :
打开 android/app/src/main/AndroidManifest.xml,在 <manifest> 标签内添加以下权限:
XML
<uses-permission android:name="android.permission.CAMERA" />
<uses-permission android:name="android.permission.RECORD_AUDIO" />
<uses-permission android:name="android.permission.INTERNET" />

iOS 配置 :
打开 ios/Runner/Info.plist,在 <dict> 标签内添加相机和麦克风的使用描述:

XML
<!-- 2. 👇 插入在这里:直播所需的权限配置 (直接复制下面这6行) 👇 -->
<key>NSCameraUsageDescription</key>
<string>需要访问您的相机以进行直播推流</string>
<key>NSMicrophoneUsageDescription</key>
<string>需要访问您的麦克风以进行直播推流</string>
<!-- 2. 👆 权限配置结束 👆 -->
第二步:安装依赖
在你的 Flutter 项目根目录下的 pubspec.yaml 文件中,添加推流插件:
XML
dependencies:
flutter:
sdk: flutter
apivideo_live_stream: ^1.2.0 #最新版本号 # 建议去 pub.dev 查看并填入最新版本
permission_handler: ^12.0.1 #检测工具
保存后在终端运行 flutter pub get 安装依赖。
第三步:推流页面完整代码实现
以下是一个包含权限检查、推流控制器初始化、摄像头预览画面、开始推流 的完整页面代码。你可以直接将其复制到你的 lib 目录下运行(记得替换真实的 RTMP 推流地址):
Dart
import 'dart:async';
import 'package:flutter/material.dart';
import 'package:apivideo_live_stream/apivideo_live_stream.dart';
import 'package:permission_handler/permission_handler.dart'; // 用于权限检查
void main() {
runApp(const LivePushPage());
}
class LivePushPage extends StatefulWidget {
const LivePushPage({super.key});
@override
State<LivePushPage> createState() => _LivePushPageState();
}
class _LivePushPageState extends State<LivePushPage> {
late ApiVideoLiveStreamController _controllerStream;
final GlobalKey<ScaffoldState> _scaffoldKey = GlobalKey<ScaffoldState>();
bool _isStreaming = false;
// TODO: 替换为你自己的 RTMP 推流地址(如阿里云、腾讯云或自建 Nginx-RTMP 服务器的地址)
final String rtmpUrl = 'rtmp://你的推流服务器地址/live/streamKey';
@override
void initState() {
super.initState();
_initPermissions();
_initLiveStreamController();
}
// 1. 检查并申请权限
Future<void> _initPermissions() async {
await Permission.camera.request();
await Permission.microphone.request();
}
// 2. 初始化推流控制器
void _initLiveStreamController() {
_controllerStream = ApiVideoLiveStreamController(
initialAudioConfig: AudioConfig(), // 默认音频配置
initialVideoConfig: VideoConfig.withDefaultBitrate(
resolution: Resolution.RESOLUTION_720, // 设置分辨率为 720P
),
initialCameraPosition: CameraPosition.front, // 默认前置摄像头
onConnectionSuccess: () {
debugPrint('推流连接成功');
},
onConnectionFailed: (error) {
debugPrint('推流连接失败: $error');
},
onDisconnection: () {
debugPrint('推流已断开');
setState(() => _isStreaming = false);
},
onError: (error) {
debugPrint('推流发生错误: $error');
},
);
_controllerStream.initialize();
}
// 3. 开始/停止推流
Future<void> _toggleStreaming() async {
if (_isStreaming) {
await _controllerStream.stopStreaming();
setState(() => _isStreaming = false);
} else {
try {
await _controllerStream.startStreaming(
url: rtmpUrl,
streamKey: 'test_stream_key', // 部分服务器需要
);
setState(() => _isStreaming = true);
} catch (e) {
debugPrint('开始推流失败: $e');
}
}
}
@override
void dispose() {
_controllerStream.stopStreaming();
_controllerStream.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) {
return Scaffold(
key: _scaffoldKey,
backgroundColor: Colors.black,
body: Stack(
children: [
// 4. 主播预览画面
SizedBox.expand(
child: FittedBox(
fit: BoxFit.cover,
child: SizedBox(
width: MediaQuery.of(context).size.width,
child: ApiVideoCameraPreview(
key: _scaffoldKey,
controller: _controllerStream,
),
),
),
),
// 底部控制按钮
Positioned(
bottom: 50,
left: 0,
right: 0,
child: Center(
child: ElevatedButton(
onPressed: _toggleStreaming,
style: ElevatedButton.styleFrom(
backgroundColor: _isStreaming ? Colors.red : Colors.green,
padding: const EdgeInsets.symmetric(horizontal: 40, vertical: 15),
),
child: Text(_isStreaming ? '停止推流' : '开始推流'),
),
),
),
],
),
);
}
}