Flutter直播功能插件flutter_live的使用
Flutter直播功能插件flutter_live的使用

跨平台(iOS+Android)多协议(RTMP/HTTP-FLV/HLS/WebRTC)直播播放器, Flutter+SRS。
Live Streaming player, iOS+Android, RTMP/HTTP-FLV/HLS/WebRTC, by Flutter+SRS.
使用方法
国内设置代理:
export FLUTTER_STORAGE_BASE_URL=https://storage.flutter-io.cn &&
export PUB_HOSTED_URL=https://pub.flutter-io.cn
编译和运行SRS直播(iOS可以从这里安装):
git clone https://github.com/ossrs/flutter_live.git &&
cd flutter_live/example && flutter run
警告: 不支持iOS模拟器, #14647.
示例代码完整示例
以下是一个完整的示例代码,展示如何使用flutter_live
插件进行直播播放和发布。
示例代码文件路径
example/lib/main.dart
示例代码内容
import 'package:flutter/material.dart';
import 'package:package_info/package_info.dart';
import 'package:flutter_live/flutter_live.dart' as flutter_live;
import 'package:flutter_webrtc/flutter_webrtc.dart' as webrtc;
import 'package:fijkplayer/fijkplayer.dart' as fijkplayer;
import 'package:camera_with_rtmp/camera.dart' as camera;
void main() {
runApp(MaterialApp(debugShowCheckedModeBanner: false, home: Home()));
}
class Home extends StatefulWidget {
[@override](/user/override)
State createState() => _HomeState();
}
class _HomeState extends State<Home> {
// 平台信息。
PackageInfo _info = PackageInfo(version: '0.0.0', buildNumber: '0');
// 播放或发布的URL。
String _url; // 最终的URL,应等于_controller.text
final TextEditingController _urlController = TextEditingController();
// 对于发布者。
bool _isPublish = false;
bool _isPublishing = false;
// 发布者的控制器。
camera.CameraController _cameraController = null;
[@override](/user/override)
void initState() {
super.initState();
_urlController.text = _url;
_urlController.addListener(_onUserEditUrl);
PackageInfo.fromPlatform().then((info) {
setState(() { _info = info; });
}).catchError((e) {
print('Platform error $e');
});
}
[@override](/user/override)
void dispose() {
super.dispose();
_urlController.dispose();
disposeCamera();
print('Main state disposed');
}
void _onUserEditUrl() {
print('User edit event url=$_url, text=${_urlController.text}');
if (_url != _urlController.text) {
setState(() {
_url = _urlController.text;
});
}
}
void _onUserSelectUrl(String v) {
print('User select $v, url=$_url, text=${_urlController.text}');
if (_url != v) {
setState(() {
_urlController.text = _url = v;
});
}
}
bool isUrlValid() {
return _url != null && _url.contains('://');
}
void disposeCamera() async {
if (_cameraController == null) {
return;
}
_isPublishing = false;
await _cameraController.stopVideoStreaming();
await _cameraController.dispose();
_cameraController = null;
print('Camera disposed, publish=$_isPublish, publishing=$_isPublishing');
}
void stopPublish() async {
await disposeCamera();
setState(() { });
print('Stop publish url=$_url, publishing=$_isPublishing, controller=${_cameraController?.value.isInitialized}');
}
void _onStartPlayOrPublish(BuildContext context) async {
if (!isUrlValid()) {
print('Invalid url $_url');
return;
}
print('${_isPublishing? "Stop":""} ${_isPublish? "Publish":"Play"} url=$_url, publishing=$_isPublishing');
// 对于播放器。
if (!_isPublish) {
Navigator.push(context, MaterialPageRoute(builder: (context) {
return _url.startsWith('webrtc://')? WebRTCStreamingPlayer(_url) : LiveStreamingPlayer(_url);
}));
return;
}
// 对于发布者,停止发布。
if (_isPublishing) {
stopPublish();
return;
}
// 对于发布者,发布RTMP流。
if (_url.startsWith('rtmp://')) {
stopPublish();
var cameras = await camera.availableCameras();
if (cameras.isEmpty) {
print('Error: No cameras');
return;
}
camera.CameraDescription desc = cameras[0];
for (var c in cameras) {
if (c.lensDirection == camera.CameraLensDirection.front) {
desc = c;
break;
}
}
print('Use camera ${desc.name} ${desc.lensDirection}');
_cameraController = camera.CameraController(desc, camera.ResolutionPreset.low);
_cameraController.addListener(() {
setState(() { print('got camera event'); });
});
await _cameraController.initialize();
print('Camera initialized ok');
await _cameraController.startVideoStreaming(_url, bitrate: 300 * 1000);
print('Start streaming to $_url');
setState(() { _isPublishing = true; });
}
}
void _onSwitchPublish(bool v) {
if (!v) {
stopPublish();
}
setState(() { _isPublish = v; });
}
[@override](/user/override)
Widget build(BuildContext context) {
var _onStartPlayOrPublish = () {
this._onStartPlayOrPublish(context);
};
return Scaffold(
appBar: AppBar(title: Text('SRS: Flutter Live Streaming')),
body: ListView(children: [
UrlInputDisplay(_urlController),
ControlDisplay(isUrlValid(), _onStartPlayOrPublish, _isPublish, _isPublishing, _onSwitchPublish),
CameraDisplay(_isPublish, _cameraController),
DemoUrlsDisplay(_url, _onUserSelectUrl, _isPublish),
PlatformDisplay(_info),
]),
);
}
}
// 输入URL的显示组件
class UrlInputDisplay extends StatelessWidget {
final TextEditingController _controller;
UrlInputDisplay(this._controller);
[@override](/user/override)
Widget build(BuildContext context) {
return Container(
child: TextField(
controller: _controller, autofocus: false,
decoration: InputDecoration(hintText: 'Please select or input url...')
),
padding: EdgeInsets.fromLTRB(5, 0, 5, 0),
);
}
}
// 演示URL的显示组件
class DemoUrlsDisplay extends StatelessWidget {
final String _url;
final ValueChanged<String> _onUserSelectUrl;
final bool _isPublish;
DemoUrlsDisplay(this._url, this._onUserSelectUrl, this._isPublish);
[@override](/user/override)
Widget build(BuildContext context) {
return Container(child:
_isPublish? Column(children: [
ListTile(
title: Column(crossAxisAlignment: CrossAxisAlignment.start, children: [
Text('RTMP', style: TextStyle(fontWeight: FontWeight.bold)),
Text(flutter_live.FlutterLive.rtmp_publish, style: TextStyle(color: Colors.grey[500])),
]),
onTap: () => _onUserSelectUrl(flutter_live.FlutterLive.rtmp_publish), contentPadding: EdgeInsets.zero,
leading: Radio(value: flutter_live.FlutterLive.rtmp_publish, groupValue: _url, onChanged: _onUserSelectUrl),
),
ListTile(
title: Column(crossAxisAlignment: CrossAxisAlignment.start, children: [
Text('RTMP', style: TextStyle(fontWeight: FontWeight.bold)),
Text(flutter_live.FlutterLive.rtmp_publish2, style: TextStyle(color: Colors.grey[500])),
]),
onTap: () => _onUserSelectUrl(flutter_live.FlutterLive.rtmp_publish2), contentPadding: EdgeInsets.zero,
leading: Radio(value: flutter_live.FlutterLive.rtmp_publish2, groupValue: _url, onChanged: _onUserSelectUrl),
),
],) : Column(children: [
ListTile(
title: Column(crossAxisAlignment: CrossAxisAlignment.start, children: [
Text('RTMP', style: TextStyle(fontWeight: FontWeight.bold)),
Text(flutter_live.FlutterLive.rtmp, style: TextStyle(color: Colors.grey[500])),
]),
onTap: () => _onUserSelectUrl(flutter_live.FlutterLive.rtmp), contentPadding: EdgeInsets.zero,
leading: Radio(value: flutter_live.FlutterLive.rtmp, groupValue: _url, onChanged: _onUserSelectUrl),
),
ListTile(
title: Column(crossAxisAlignment: CrossAxisAlignment.start, children: [
Text('HLS', style: TextStyle(fontWeight: FontWeight.bold)),
Text(flutter_live.FlutterLive.hls, style: TextStyle(color: Colors.grey[500])),
]),
onTap: () => _onUserSelectUrl(flutter_live.FlutterLive.hls), contentPadding: EdgeInsets.zero,
leading: Radio(value: flutter_live.FlutterLive.hls, groupValue: _url, onChanged: _onUserSelectUrl),
),
ListTile(
title: Column(crossAxisAlignment: CrossAxisAlignment.start, children: [
Text('HTTP-FLV', style: TextStyle(fontWeight: FontWeight.bold)),
Text(flutter_live.FlutterLive.flv, style: TextStyle(color: Colors.grey[500])),
]),
onTap: () => _onUserSelectUrl(flutter_live.FlutterLive.flv), contentPadding: EdgeInsets.zero,
leading: Radio(value: flutter_live.FlutterLive.flv, groupValue: _url, onChanged: _onUserSelectUrl),
),
ListTile(
title: Column(crossAxisAlignment: CrossAxisAlignment.start, children: [
Text('WebRTC', style: TextStyle(fontWeight: FontWeight.bold)),
Text(flutter_live.FlutterLive.rtc, style: TextStyle(color: Colors.grey[500], fontSize: 15)),
]),
onTap: () => _onUserSelectUrl(flutter_live.FlutterLive.rtc), contentPadding: EdgeInsets.zero,
leading: Radio(value: flutter_live.FlutterLive.rtc, groupValue: _url, onChanged: _onUserSelectUrl),
),
ListTile(
title: Column(crossAxisAlignment: CrossAxisAlignment.start, children: [
Text('HTTPS-FLV', style: TextStyle(fontWeight: FontWeight.bold)),
Container(
child: Text(flutter_live.FlutterLive.flvs, style: TextStyle(color: Colors.grey[500], fontSize: 15)),
padding: EdgeInsets.only(top: 3, bottom:3),
),
]),
onTap: () => _onUserSelectUrl(flutter_live.FlutterLive.flvs), contentPadding: EdgeInsets.zero,
leading: Radio(value: flutter_live.FlutterLive.flvs, groupValue: _url, onChanged: _onUserSelectUrl),
),
ListTile(
title: Column(crossAxisAlignment: CrossAxisAlignment.start, children: [
Text('HTTPS-HLS', style: TextStyle(fontWeight: FontWeight.bold)),
Container(
child: Text(flutter_live.FlutterLive.hlss, style: TextStyle(color: Colors.grey[500], fontSize: 14)),
padding: EdgeInsets.only(top: 3, bottom: 3),
),
]),
onTap: () => _onUserSelectUrl(flutter_live.FlutterLive.hlss), contentPadding: EdgeInsets.zero,
leading: Radio(value: flutter_live.FlutterLive.hlss, groupValue: _url, onChanged: _onUserSelectUrl),
),
]),
);
}
}
// 控制按钮的显示组件
class ControlDisplay extends StatelessWidget {
final bool _urlAvailable;
final VoidCallback _onStartPlayOrPublish;
final bool _isPubslish;
final bool _isPublishing;
final ValueChanged<bool> _onSwitchPublish;
ControlDisplay(this._urlAvailable, this._onStartPlayOrPublish, this._isPubslish, this._isPublishing, this._onSwitchPublish);
[@override](/user/override)
Widget build(BuildContext context) {
var actionText = () {
if (_isPublishing) {
return 'Stop';
}
return _isPubslish? 'Publish' : 'Play';
};
return Row(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: [
Row(
children: [
Text('Publish'),
Switch(value: _isPubslish, onChanged: _onSwitchPublish),
],
),
Container(
width: 120,
child:ElevatedButton(
child: Text(actionText()),
onPressed: _urlAvailable? _onStartPlayOrPublish : null,
),
),
],
);
}
}
// 摄像头预览的显示组件
class CameraDisplay extends StatelessWidget {
final bool _isPublish;
final camera.CameraController _cameraController;
CameraDisplay(this._isPublish, this._cameraController);
[@override](/user/override)
Widget build(BuildContext context) {
if (!_isPublish) {
return Container();
}
if (_cameraController == null) {
return Container();
}
if (!_cameraController.value.isInitialized) {
return Container(child: Center(child: Text(
'Camera not available', style: TextStyle(color: Colors.red[500]),
)));
}
return AspectRatio(
aspectRatio: _cameraController.value.aspectRatio,
child: camera.CameraPreview(_cameraController)
);
}
}
// 平台信息的显示组件
class PlatformDisplay extends StatelessWidget {
final PackageInfo _info;
PlatformDisplay(this._info);
[@override](/user/override)
Widget build(BuildContext context) {
return Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [Text('SRS/v${_info.version}+${_info.buildNumber}')],
);
}
}
// 直播播放器
class LiveStreamingPlayer extends StatefulWidget {
final String _url;
LiveStreamingPlayer(this._url);
[@override](/user/override)
_LiveStreamingPlayerState createState() => _LiveStreamingPlayerState();
}
class _LiveStreamingPlayerState extends State<LiveStreamingPlayer> {
final flutter_live.RealtimePlayer _player = flutter_live.RealtimePlayer(fijkplayer.FijkPlayer());
[@override](/user/override)
void initState() {
super.initState();
_player.initState();
autoPlay();
}
void autoPlay() async {
// 自动开始播放直播流。
await _player.play(widget._url);
}
[@override](/user/override)
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text('SRS Live Streaming')),
body: fijkplayer.FijkView(
player: _player.fijk, panelBuilder: fijkplayer.fijkPanel2Builder(),
fsFit: fijkplayer.FijkFit.fill
),
);
}
[@override](/user/override)
void dispose() {
super.dispose();
_player.dispose();
}
}
// WebRTC流播放器
class WebRTCStreamingPlayer extends StatefulWidget {
final String _url;
WebRTCStreamingPlayer(this._url);
[@override](/user/override)
State<StatefulWidget> createState() => _WebRTCStreamingPlayerState();
}
class _WebRTCStreamingPlayerState extends State<WebRTCStreamingPlayer> {
bool _loudspeaker = true;
final webrtc.RTCVideoRenderer _video = webrtc.RTCVideoRenderer();
final flutter_live.WebRTCPlayer _player = flutter_live.WebRTCPlayer();
[@override](/user/override)
void initState() {
super.initState();
_player.initState();
autoPlay();
}
void autoPlay() async {
await _video.initialize();
// 渲染远程流时更新视频源。
_player.onRemoteStream = (webrtc.MediaStream stream) {
setState(() { _video.srcObject = stream; });
};
// 自动播放WebRTC流。
await _player.play(widget._url);
}
[@override](/user/override)
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text('SRS WebRTC Streaming')),
body: GestureDetector(onTap: _switchLoudspeaker, child: Container(
child: webrtc.RTCVideoView(_video), decoration: BoxDecoration(color: Colors.grey[500])
)),
);
}
void _switchLoudspeaker() {
print('setSpeakerphoneOn: $_loudspeaker(${_loudspeaker? "Loudspeaker":"Earpiece"})');
flutter_live.FlutterLive.setSpeakerphoneOn(_loudspeaker);
_loudspeaker = !_loudspeaker;
}
[@override](/user/override)
void dispose() {
super.dispose();
_video.dispose();
_player.dispose();
}
}
更多关于Flutter直播功能插件flutter_live的使用的实战系列教程也可以访问 https://www.itying.com/category-92-b0.html
更多关于Flutter直播功能插件flutter_live的使用的实战系列教程也可以访问 https://www.itying.com/category-92-b0.html
flutter_live
是一个用于在 Flutter 应用中实现直播功能的插件。它可以帮助你快速集成直播功能,支持多种直播协议和平台。以下是如何使用 flutter_live
插件的基本步骤:
1. 添加依赖
首先,你需要在 pubspec.yaml
文件中添加 flutter_live
插件的依赖:
dependencies:
flutter:
sdk: flutter
flutter_live: ^1.0.0 # 请使用最新版本
然后运行 flutter pub get
来获取依赖。
2. 导入插件
在你的 Dart 文件中导入 flutter_live
插件:
import 'package:flutter_live/flutter_live.dart';
3. 初始化直播
在使用直播功能之前,你需要初始化 flutter_live
插件。通常你可以在 main.dart
或某个初始化函数中进行初始化:
void main() async {
WidgetsFlutterBinding.ensureInitialized();
await FlutterLive.initialize();
runApp(MyApp());
}
4. 创建直播播放器
你可以使用 LivePlayer
来播放直播流。以下是一个简单的示例:
class LiveStreamPage extends StatefulWidget {
@override
_LiveStreamPageState createState() => _LiveStreamPageState();
}
class _LiveStreamPageState extends State<LiveStreamPage> {
LivePlayerController _controller;
@override
void initState() {
super.initState();
_controller = LivePlayerController();
_controller.initialize().then((_) {
setState(() {});
});
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('Live Stream'),
),
body: Center(
child: _controller.value.isInitialized
? AspectRatio(
aspectRatio: _controller.value.aspectRatio,
child: LivePlayer(controller: _controller),
)
: CircularProgressIndicator(),
),
floatingActionButton: FloatingActionButton(
onPressed: () {
_controller.play('https://your-live-stream-url.com');
},
child: Icon(Icons.play_arrow),
),
);
}
@override
void dispose() {
_controller.dispose();
super.dispose();
}
}
5. 创建直播推流
如果你需要实现直播推流功能,可以使用 LivePusher
。以下是一个简单的示例:
class LivePushPage extends StatefulWidget {
@override
_LivePushPageState createState() => _LivePushPageState();
}
class _LivePushPageState extends State<LivePushPage> {
LivePusherController _controller;
@override
void initState() {
super.initState();
_controller = LivePusherController();
_controller.initialize().then((_) {
setState(() {});
});
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('Live Push'),
),
body: Center(
child: _controller.value.isInitialized
? AspectRatio(
aspectRatio: _controller.value.aspectRatio,
child: LivePusher(controller: _controller),
)
: CircularProgressIndicator(),
),
floatingActionButton: FloatingActionButton(
onPressed: () {
_controller.startPush('rtmp://your-rtmp-server-url.com/live/stream');
},
child: Icon(Icons.videocam),
),
);
}
@override
void dispose() {
_controller.dispose();
super.dispose();
}
}
6. 处理直播事件
你可以监听直播播放器或推流器的事件,例如播放状态、错误等:
_controller.addListener(() {
if (_controller.value.hasError) {
print('Error: ${_controller.value.errorDescription}');
}
if (_controller.value.isPlaying) {
print('Stream is playing');
}
});