Flutter直播功能插件flutter_live的使用

发布于 1周前 作者 songsunli 来自 Flutter

Flutter直播功能插件flutter_live的使用

Pub Version

跨平台(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

1 回复

更多关于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');
  }
});
回到顶部
AI 助手
你好,我是IT营的 AI 助手
您可以尝试点击下方的快捷入口开启体验!