Flutter下实现低延迟的跨平台RTSP/RTMP播放

  • 2019 年 10 月 4 日
  • 筆記

为什么要用Flutter?

Flutter是谷歌的移动UI框架,可以快速在iOS和Android上构建高质量的原生用户界面。 Flutter可以与现有的代码一起工作。在全世界,Flutter正在被越来越多的开发者和组织使用,并且Flutter是完全免费、开源的。

Flutter有哪些与众不同

1. Beautiful – Flutter 允许你控制屏幕上的每一寸像素,这让「设计」不用再对「实现」妥协;

2. Fast – 一个应用不卡顿的标准是什么,你可能会说 16ms 抑或是 60fps,这对桌面端应用或者移动端应用来说已足够,但当面对广阔的 AR/VR 领域,60fps 仍然会成为使人脑产生眩晕的瓶颈,而 Flutter 的目标远不止 60fps;借助 Dart 支持的 AOT 编译以及 Skia 的绘制,Flutter 可以运行的很快;

3. Productive – 前端开发可能已经习惯的开发中 hot reload 模式,但这一特性在移动开发中还算是个新鲜事。Flutter 提供有状态的 hot reload 开发模式,并允许一套 codebase 运行于多端;其他的,再比如开发采用 JIT 编译与发布的 AOT 编译,都使得开发者在开发应用时可以更加高效;

4. Open – Dart / Skia / Flutter (Framework),这些都是开源的,Flutter 与 Dart 团队也对包括 Web 在内的多种技术持开放态度,只要是优秀的他们都愿意借鉴吸收。而在生态建设上,Flutter 回应 GitHub Issue 的速度更是让人惊叹,因为是真的快(closed 状态的 issue 平均解决时间为 0.29天);

除了支持APICloud, Unity3d, React Native外,为什么要做Flutter下的RTSP/RTMP播放器

首先,Flutter则是依靠Flutter Engine虚拟机在iOS和Android上运行,开发人员可以通过Flutter框架和API在内部进行交互。Flutter Engine使用C/C++编写,具有低延迟输入和高帧速率的特点,不像Unity3d一样,我们是回调YUV/RGB数据,在Unity3d里面绘制,Flutter直接调用native SDK,效率更高。

其次,客户和开发者驱动,Flutter发展至今,目前还没有个像样的RTSP或RTMP播放器,一个播放器,不是说,有个界面,有个开始、停止按钮就可以了,一个好用的直播播放器,对功能和性能属性要求很高,特别是稳定性和低延迟这块,不谦虚的说,大牛直播SDK(Github)的这款Flutter下的RTSP/RTMP播放器,可能是首款功能强大、真正好用的Flutter RTSP/RTMP直播播放SDK。

Android和iOS手机上RTSP/RTMP播放效果:

界面截图:

上接口:

//  //  smartplayer.dart  //  smartplayer  //  //  GitHub: https://github.com/daniulive/SmarterStreaming  //  website: https://www.daniulive.com  //  //  Created by daniulive on 2019/02/25.  //  Copyright © 2014~2019 daniulive. All rights reserved.  //    import 'dart:async';  import 'dart:convert';    import 'package:flutter/services.dart';    class EVENTID {    static const EVENT_DANIULIVE_COMMON_SDK = 0x00000000;    static const EVENT_DANIULIVE_PLAYER_SDK = 0x01000000;    static const EVENT_DANIULIVE_PUBLISHER_SDK = 0x02000000;      static const EVENT_DANIULIVE_ERC_PLAYER_STARTED =        EVENT_DANIULIVE_PLAYER_SDK | 0x1;    static const EVENT_DANIULIVE_ERC_PLAYER_CONNECTING =        EVENT_DANIULIVE_PLAYER_SDK | 0x2;    static const EVENT_DANIULIVE_ERC_PLAYER_CONNECTION_FAILED =        EVENT_DANIULIVE_PLAYER_SDK | 0x3;    static const EVENT_DANIULIVE_ERC_PLAYER_CONNECTED =        EVENT_DANIULIVE_PLAYER_SDK | 0x4;    static const EVENT_DANIULIVE_ERC_PLAYER_DISCONNECTED =        EVENT_DANIULIVE_PLAYER_SDK | 0x5;    static const EVENT_DANIULIVE_ERC_PLAYER_STOP =        EVENT_DANIULIVE_PLAYER_SDK | 0x6;    static const EVENT_DANIULIVE_ERC_PLAYER_RESOLUTION_INFO =        EVENT_DANIULIVE_PLAYER_SDK | 0x7;    static const EVENT_DANIULIVE_ERC_PLAYER_NO_MEDIADATA_RECEIVED =        EVENT_DANIULIVE_PLAYER_SDK | 0x8;    static const EVENT_DANIULIVE_ERC_PLAYER_SWITCH_URL =        EVENT_DANIULIVE_PLAYER_SDK | 0x9;    static const EVENT_DANIULIVE_ERC_PLAYER_CAPTURE_IMAGE =        EVENT_DANIULIVE_PLAYER_SDK | 0xA;      static const EVENT_DANIULIVE_ERC_PLAYER_RECORDER_START_NEW_FILE =        EVENT_DANIULIVE_PLAYER_SDK | 0x21; /*录像写入新文件*/    static const EVENT_DANIULIVE_ERC_PLAYER_ONE_RECORDER_FILE_FINISHED =        EVENT_DANIULIVE_PLAYER_SDK | 0x22; /*一个录像文件完成*/      static const EVENT_DANIULIVE_ERC_PLAYER_START_BUFFERING =        EVENT_DANIULIVE_PLAYER_SDK | 0x81;    static const EVENT_DANIULIVE_ERC_PLAYER_BUFFERING =        EVENT_DANIULIVE_PLAYER_SDK | 0x82;    static const EVENT_DANIULIVE_ERC_PLAYER_STOP_BUFFERING =        EVENT_DANIULIVE_PLAYER_SDK | 0x83;      static const EVENT_DANIULIVE_ERC_PLAYER_DOWNLOAD_SPEED =        EVENT_DANIULIVE_PLAYER_SDK | 0x91;  }    typedef SmartEventCallback = void Function(int, String, String, String);    class SmartPlayerController {    MethodChannel _channel;    EventChannel _eventChannel;    SmartEventCallback _eventCallback;      void init(int id) {      _channel = MethodChannel('smartplayer_plugin_$id');      _eventChannel = EventChannel('smartplayer_event_$id');      _eventChannel.receiveBroadcastStream().listen(_onEvent, onError: _onError);    }      void setEventCallback(SmartEventCallback callback) {      _eventCallback = callback;    }      void _onEvent(Object event) {      if (event != null) {        Map valueMap = json.decode(event);        String param = valueMap['param'];        onSmartEvent(param);      }    }      void _onError(Object error) {      // print('error:'+ error);    }      Future<dynamic> _smartPlayerCall(String funcName) async {      var ret = await _channel.invokeMethod(funcName);      return ret;    }      Future<dynamic> _smartPlayerCallInt(String funcName, int param) async {      var ret = await _channel.invokeMethod(funcName, {        'intParam': param,      });      return ret;    }      Future<dynamic> _smartPlayerCallIntInt(        String funcName, int param1, int param2) async {      var ret = await _channel.invokeMethod(funcName, {        'intParam': param1,        'intParam2': param2,      });      return ret;    }      Future<dynamic> _smartPlayerCallString(String funcName, String param) async {      var ret = await _channel.invokeMethod(funcName, {        'strParam': param,      });      return ret;    }      /// 设置解码方式 false 软解码 true 硬解码 默认为false    /// </summary>    /// <param name="isHwDecoder"></param>    Future<dynamic> setVideoDecoderMode(int isHwDecoder) async {      return _smartPlayerCallInt('setVideoDecoderMode', isHwDecoder);    }      /// <summary>    /// 设置音频输出模式: if 0: 自动选择; if with 1: audiotrack模式, 此接口仅限于Android平台使用    /// </summary>    /// <param name="use_audiotrack"></param>    Future<dynamic> setAudioOutputType(int useAudiotrack) async {      return _smartPlayerCallInt('setAudioOutputType', useAudiotrack);    }      /// <summary>    /// 设置播放端缓存大小, 默认200毫秒    /// </summary>    /// <param name="buffer"></param>    Future<dynamic> setBuffer(int buffer) async {      return _smartPlayerCallInt('setBuffer', buffer);    }      /// <summary>    /// 接口可实时调用:设置是否实时静音,1:静音; 0: 取消静音    /// </summary>    /// <param name="is_mute"></param>    Future<dynamic> setMute(int isMute) async {      return _smartPlayerCallInt('setMute', isMute);    }      /// <summary>    /// 设置RTSP TCP模式, 1: TCP; 0: UDP    /// </summary>    /// <param name="is_using_tcp"></param>    Future<dynamic> setRTSPTcpMode(int isUsingTcp) async {      return _smartPlayerCallInt('setRTSPTcpMode', isUsingTcp);    }      /// <summary>    /// 设置RTSP超时时间, timeout单位为秒,必须大于0    /// </summary>    /// <param name="timeout"></param>    Future<dynamic> setRTSPTimeout(int timeout) async {      return _smartPlayerCallInt('setRTSPTimeout', timeout);    }      /// <summary>    /// 设置RTSP TCP/UDP自动切换    /// 对于RTSP来说,有些可能支持rtp over udp方式,有些可能支持使用rtp over tcp方式.    /// 为了方便使用,有些场景下可以开启自动尝试切换开关, 打开后如果udp无法播放,sdk会自动尝试tcp, 如果tcp方式播放不了,sdk会自动尝试udp.    /// </summary>    /// <param name="is_auto_switch_tcp_udp"></param>    Future<dynamic> setRTSPAutoSwitchTcpUdp(int is_auto_switch_tcp_udp) async {      return _smartPlayerCallInt('setRTSPAutoSwitchTcpUdp', is_auto_switch_tcp_udp);    }      /// <summary>    /// 设置快速启动该模式,    /// </summary>    /// <param name="is_fast_startup"></param>    Future<dynamic> setFastStartup(int isFastStartup) async {      return _smartPlayerCallInt('setFastStartup', isFastStartup);    }      /// <summary>    /// 设置超低延迟模式 false不开启 true开启 默认false    /// </summary>    /// <param name="mode"></param>    Future<dynamic> setPlayerLowLatencyMode(int mode) async {      return _smartPlayerCallInt('setPlayerLowLatencyMode', mode);    }      /// <summary>    /// 设置视频垂直反转    /// </summary>    /// <param name="is_flip"></param>    Future<dynamic> setFlipVertical(int is_flip) async {      return _smartPlayerCallInt('setFlipVertical', is_flip);    }      /// <summary>    /// 设置视频水平反转    /// </summary>    /// <param name="is_flip"></param>    Future<dynamic> setFlipHorizontal(int is_flip) async {      return _smartPlayerCallInt('setFlipHorizontal', is_flip);    }      /// <summary>    /// 设置顺时针旋转, 注意除了0度之外, 其他角度都会额外消耗性能    /// degress: 当前支持 0度,90度, 180度, 270度 旋转    /// </summary>    /// <param name="degress"></param>    Future<dynamic> setRotation(int degress) async {      return _smartPlayerCallInt('setRotation', degress);    }      /// <summary>    /// 设置是否回调下载速度    /// is_report: if 1: 上报下载速度, 0: 不上报.    /// report_interval: 上报间隔,以秒为单位,>0.    /// </summary>    /// <param name="is_report"></param>    /// <param name="report_interval"></param>    Future<dynamic> setReportDownloadSpeed(        int isReport, int reportInterval) async {      return _smartPlayerCallIntInt(          'setReportDownloadSpeed', isReport, reportInterval);    }      /// <summary>    /// Set playback orientation(设置播放方向),此接口仅适用于Android平台    /// </summary>    /// <param name="surOrg"></param>    /// surOrg: current orientation,  PORTRAIT 1, LANDSCAPE with 2    Future<dynamic> setOrientation(int surOrg) async {      return _smartPlayerCallInt('setOrientation', surOrg);    }      /// <summary>    /// 设置是否需要在播放或录像过程中快照    /// </summary>    /// <param name="is_save_image"></param>    Future<dynamic> setSaveImageFlag(int isSaveImage) async {      return _smartPlayerCallInt('setSaveImageFlag', isSaveImage);    }      /// <summary>    /// 播放或录像过程中快照    /// </summary>    /// <param name="imageName"></param>    Future<dynamic> saveCurImage(String imageName) async {      return _smartPlayerCallString('saveCurImage', imageName);    }      /// <summary>    /// 播放或录像过程中,快速切换url    /// </summary>    /// <param name="uri"></param>    Future<dynamic> switchPlaybackUrl(String uri) async {      return _smartPlayerCallString('switchPlaybackUrl', uri);    }      /// <summary>    /// 创建录像存储路径    /// </summary>    /// <param name="path"></param>    Future<dynamic> createFileDirectory(String path) async {      return _smartPlayerCallString('createFileDirectory', path);    }      /// <summary>    /// 设置录像存储路径    /// </summary>    /// <param name="path"></param>    Future<dynamic> setRecorderDirectory(String path) async {      return _smartPlayerCallString('setRecorderDirectory', path);    }      /// <summary>    /// 设置单个录像文件大小    /// </summary>    /// <param name="size"></param>    Future<dynamic> setRecorderFileMaxSize(int size) async {      return _smartPlayerCallInt('setRecorderFileMaxSize', size);    }      /// <summary>    /// 设置录像时音频转AAC编码的开关    /// aac比较通用,sdk增加其他音频编码(比如speex, pcmu, pcma等)转aac的功能.    /// </summary>    /// <param name="is_transcode"></param>    /// is_transcode: 设置为1的话,如果音频编码不是aac,则转成aac,如果是aac,则不做转换. 设置为0的话,则不做任何转换. 默认是0.    Future<dynamic> setRecorderAudioTranscodeAAC(int is_transcode) async {      return _smartPlayerCallInt('setRecorderAudioTranscodeAAC', is_transcode);    }      /// <summary>    /// 设置播放路径    /// </summary>    Future<dynamic> setUrl(String url) async {      return _smartPlayerCallString('setUrl', url);    }      /// <summary>    /// 开始播放    /// </summary>    Future<dynamic> startPlay() async {      return _smartPlayerCall('startPlay');    }      /// <summary>    /// 停止播放    /// </summary>    Future<dynamic> stopPlay() async {      return _smartPlayerCall('stopPlay');    }      /// <summary>    /// 开始录像    /// </summary>    Future<dynamic> startRecorder() async {      return _smartPlayerCall('startRecorder');    }      /// <summary>    /// 停止录像    /// </summary>    Future<dynamic> stopRecorder() async {      return _smartPlayerCall('stopRecorder');    }      /// <summary>    /// 关闭播放    /// </summary>    Future<dynamic> dispose() async {      return await _channel.invokeMethod('dispose');    }      void onSmartEvent(String param) {      if (!param.contains(",")) {        print("[onNTSmartEvent] android传递参数错误");        return;      }        List<String> strs = param.split(',');        String code = strs[1];      String param1 = strs[2];      String param2 = strs[3];      String param3 = strs[4];      String param4 = strs[5];        int evCode = int.parse(code);        var p1, p2, p3;      switch (evCode) {        case EVENTID.EVENT_DANIULIVE_ERC_PLAYER_STARTED:          print("开始。。");          break;        case EVENTID.EVENT_DANIULIVE_ERC_PLAYER_CONNECTING:          print("连接中。。");          break;        case EVENTID.EVENT_DANIULIVE_ERC_PLAYER_CONNECTION_FAILED:          print("连接失败。。");          break;        case EVENTID.EVENT_DANIULIVE_ERC_PLAYER_CONNECTED:          print("连接成功。。");          break;        case EVENTID.EVENT_DANIULIVE_ERC_PLAYER_DISCONNECTED:          print("连接断开。。");          break;        case EVENTID.EVENT_DANIULIVE_ERC_PLAYER_STOP:          print("停止播放。。");          break;        case EVENTID.EVENT_DANIULIVE_ERC_PLAYER_RESOLUTION_INFO:          print("分辨率信息: width: " + param1 + ", height: " + param2);          p1 = param1;          p2 = param2;          break;        case EVENTID.EVENT_DANIULIVE_ERC_PLAYER_NO_MEDIADATA_RECEIVED:          print("收不到媒体数据,可能是url错误。。");          break;        case EVENTID.EVENT_DANIULIVE_ERC_PLAYER_SWITCH_URL:          print("切换播放URL。。");          break;          case EVENTID.EVENT_DANIULIVE_ERC_PLAYER_CAPTURE_IMAGE:          print("快照: " + param1 + " 路径:" + param3);            if (int.parse(param1) == 0) {             print("截取快照成功。.");          } else {             print("截取快照失败。.");          }          p1 = param1;          p2 = param3;          break;          case EVENTID.EVENT_DANIULIVE_ERC_PLAYER_RECORDER_START_NEW_FILE:          print("[record]开始一个新的录像文件 : " + param3);          p3 = param3;          break;        case EVENTID.EVENT_DANIULIVE_ERC_PLAYER_ONE_RECORDER_FILE_FINISHED:          print("[record]已生成一个录像文件 : " + param3);          p3 = param3;          break;          case EVENTID.EVENT_DANIULIVE_ERC_PLAYER_START_BUFFERING:          print("Start_Buffering");          break;          case EVENTID.EVENT_DANIULIVE_ERC_PLAYER_BUFFERING:          print("Buffering: " + param1 + "%");          p1 = param1;          break;          case EVENTID.EVENT_DANIULIVE_ERC_PLAYER_STOP_BUFFERING:          print("Stop_Buffering");          break;          case EVENTID.EVENT_DANIULIVE_ERC_PLAYER_DOWNLOAD_SPEED:           print("download_speed:" + (double.parse(param1) * 8 / 1000).toStringAsFixed(0) + "kbps" + ", " + (double.parse(param1) / 1024).toStringAsFixed(0) + "KB/s");          p1 = param1;          break;      }      if (_eventCallback != null) {        _eventCallback(evCode, p1, p2, p3);      }    }  }

调用实例:

//  //  main.dart  //  main  //  //  GitHub: https://github.com/daniulive/SmarterStreaming  //  website: https://www.daniulive.com  //  //  Created by daniulive on 2019/02/25.  //  Copyright © 2014~2019 daniulive. All rights reserved.  //    import 'dart:io';  import 'package:flutter/services.dart';  import 'package:flutter/material.dart';  import 'package:flutter/cupertino.dart';  import 'package:flutter/foundation.dart';  import 'package:smartplayer_native_view/smartplayer.dart';  import 'package:smartplayer_native_view/smartplayer_plugin.dart';    void main() {    ///    /// 强制竖屏    ///    SystemChrome.setPreferredOrientations(        [DeviceOrientation.portraitUp, DeviceOrientation.portraitDown]);      runApp(new MyApp());  }    class MyApp extends StatefulWidget {    @override    _MyAppState createState() => new _MyAppState();  }    class _MyAppState extends State<MyApp> {    SmartPlayerController player;    double aspectRatio = 4.0 / 3.0;      //输入需要播放的RTMP/RTSP url    TextEditingController playback_url_controller_ = TextEditingController();      //Event事件回调显示    TextEditingController event_controller_ = TextEditingController();      bool is_playing_ = false;    bool is_mute_ = false;      var rotate_degrees_ = 0;      Widget smartPlayerView() {        return SmartPlayerWidget(          onSmartPlayerCreated: onSmartPlayerCreated,        );      }      @override    void initState() {       print("initState called..");      super.initState();    }      @override    void didChangeDependencies() {      print('didChangeDependencies called..');      super.didChangeDependencies();    }      @override    void deactivate() {      print('deactivate called..');      super.deactivate();    }      @override    void dispose() {      print("dispose called..");      player.dispose();      super.dispose();    }      @override    Widget build(BuildContext context) {      return MaterialApp(        home: Scaffold(            appBar: AppBar(              title: const Text('Flutter SmartPlayer Demo'),            ),            body: new SingleChildScrollView(              child: new Column(                children: <Widget>[                  new Container(                    color: Colors.black,                    child: AspectRatio(                      child: smartPlayerView(),                      aspectRatio: aspectRatio,                    ),                  ),                  new TextField(                    controller: playback_url_controller_,                    keyboardType: TextInputType.text,                    decoration: InputDecoration(                      contentPadding: EdgeInsets.all(10.0),                      icon: Icon(Icons.link),                      labelText: '请输入RTSP/RTMP url',                    ),                    autofocus: false,                  ),                  new Row(                    children: [                      new RaisedButton(                          onPressed: this.onSmartPlayerStartPlay,                          child: new Text("开始播放")),                      new Container(width: 20),                      new RaisedButton(                          onPressed: this.onSmartPlayerStopPlay,                          child: new Text("停止播放")),                      new Container(width: 20),                      new RaisedButton(                          onPressed: this.onSmartPlayerMute,                          child: new Text("实时静音")),                    ],                  ),                  new Row(                    children: [                      new RaisedButton(                          onPressed: this.onSmartPlayerSwitchUrl,                          child: new Text("实时切换URL")),                      new Container(width: 20),                      new RaisedButton(                          onPressed: this.onSmartPlayerSetRotation,                          child: new Text("实时旋转View")),                    ],                  ),                  new TextField(                    controller: event_controller_,                    keyboardType: TextInputType.text,                    decoration: InputDecoration(                      contentPadding: EdgeInsets.all(10.0),                      icon: Icon(Icons.event_note),                      labelText: 'Event状态回调',                    ),                    autofocus: false,                  ),                ],              ),            )),      );    }      void _eventCallback(int code, String param1, String param2, String param3) {      String event_str;        switch (code) {        case EVENTID.EVENT_DANIULIVE_ERC_PLAYER_STARTED:          event_str = "开始..";          break;        case EVENTID.EVENT_DANIULIVE_ERC_PLAYER_CONNECTING:          event_str = "连接中..";          break;        case EVENTID.EVENT_DANIULIVE_ERC_PLAYER_CONNECTION_FAILED:          event_str = "连接失败..";          break;        case EVENTID.EVENT_DANIULIVE_ERC_PLAYER_CONNECTED:          event_str = "连接成功..";          break;        case EVENTID.EVENT_DANIULIVE_ERC_PLAYER_DISCONNECTED:          event_str = "连接断开..";          break;        case EVENTID.EVENT_DANIULIVE_ERC_PLAYER_STOP:          event_str = "停止播放..";          break;        case EVENTID.EVENT_DANIULIVE_ERC_PLAYER_RESOLUTION_INFO:          event_str = "分辨率信息: width: " + param1 + ", height: " + param2;          setState(() {            aspectRatio = double.parse(param1) / double.parse(param2);            print('change aspectRatio:$aspectRatio');          });          break;        case EVENTID.EVENT_DANIULIVE_ERC_PLAYER_NO_MEDIADATA_RECEIVED:          event_str = "收不到媒体数据,可能是url错误..";          break;        case EVENTID.EVENT_DANIULIVE_ERC_PLAYER_SWITCH_URL:          event_str = "切换播放URL..";          break;        case EVENTID.EVENT_DANIULIVE_ERC_PLAYER_CAPTURE_IMAGE:          event_str = "快照: " + param1 + " 路径: " + param3;            if (int.parse(param1) == 0) {            print("截取快照成功。.");          } else {            print("截取快照失败。.");          }          break;        case EVENTID.EVENT_DANIULIVE_ERC_PLAYER_RECORDER_START_NEW_FILE:          event_str = "[record] new file: " + param3;          break;        case EVENTID.EVENT_DANIULIVE_ERC_PLAYER_ONE_RECORDER_FILE_FINISHED:          event_str = "[record] record finished: " + param3;          break;        case EVENTID.EVENT_DANIULIVE_ERC_PLAYER_START_BUFFERING:          //event_str = "Start Buffering";          break;        case EVENTID.EVENT_DANIULIVE_ERC_PLAYER_BUFFERING:          event_str = "Buffering: " + param1 + "%";          break;        case EVENTID.EVENT_DANIULIVE_ERC_PLAYER_STOP_BUFFERING:          //event_str = "Stop Buffering";          break;        case EVENTID.EVENT_DANIULIVE_ERC_PLAYER_DOWNLOAD_SPEED:          event_str = "download_speed:" +              (double.parse(param1) * 8 / 1000).toStringAsFixed(0) +              "kbps" +              ", " +              (double.parse(param1) / 1024).toStringAsFixed(0) +              "KB/s";          break;      }        event_controller_.text = event_str;    }      void onSmartPlayerCreated(SmartPlayerController controller) async {      player = controller;      player.setEventCallback(_eventCallback);        var ret = -1;        //设置video decoder模式      var is_video_hw_decoder = 0;      if (defaultTargetPlatform == TargetPlatform.android)      {        ret = await player.setVideoDecoderMode(is_video_hw_decoder);      }      else if(defaultTargetPlatform == TargetPlatform.iOS)      {        is_video_hw_decoder = 1;        ret = await player.setVideoDecoderMode(is_video_hw_decoder);      }        //设置缓冲时间      var play_buffer = 100;      ret = await player.setBuffer(play_buffer);        //设置快速启动      var is_fast_startup = 1;      ret = await player.setFastStartup(is_fast_startup);        //是否开启低延迟模式      var is_low_latency_mode = 0;      ret = await player.setPlayerLowLatencyMode(is_low_latency_mode);        //set report download speed(默认5秒一次回调 用户可自行调整report间隔)      ret = await player.setReportDownloadSpeed(1, 2);        //设置RTSP超时时间  		var rtsp_timeout = 10;  	  ret = await player.setRTSPTimeout(rtsp_timeout);        var is_auto_switch_tcp_udp = 1;  		ret = await player.setRTSPAutoSwitchTcpUdp(is_auto_switch_tcp_udp);        // 设置RTSP TCP模式  		//ret = await player.setRTSPTcpMode(1);        //第一次启动 为方便测试 设置个初始url      playback_url_controller_.text = "rtmp://live.hkstv.hk.lxdns.com/live/hks2";    }      Future<void> onSmartPlayerStartPlay() async {      var ret = -1;        if (playback_url_controller_.text.length < 8) {        playback_url_controller_.text =            "rtmp://live.hkstv.hk.lxdns.com/live/hks1"; //给个初始url      }        //实时静音设置      ret = await player.setMute(is_mute_ ? 1 : 0);        if (!is_playing_) {        ret = await player.setUrl(playback_url_controller_.text);        ret = await player.startPlay();          if (ret == 0) {          is_playing_ = true;        }      }    }      Future<void> onSmartPlayerStopPlay() async {      if (is_playing_) {        await player.stopPlay();        playback_url_controller_.clear();        is_playing_ = false;        is_mute_ = false;      }    }      Future<void> onSmartPlayerMute() async {      if (is_playing_) {        is_mute_ = !is_mute_;        await player.setMute(is_mute_ ? 1 : 0);      }    }      Future<void> onSmartPlayerSwitchUrl() async {      if (is_playing_) {        if (playback_url_controller_.text.length < 8) {          playback_url_controller_.text =              "rtmp://live.hkstv.hk.lxdns.com/live/hks1";        }          await player.switchPlaybackUrl(playback_url_controller_.text);      }    }      Future<void> onSmartPlayerSetRotation() async {      if (is_playing_) {        rotate_degrees_ += 90;        rotate_degrees_ = rotate_degrees_ % 360;          if (0 == rotate_degrees_) {          print("旋转90度");        } else if (90 == rotate_degrees_) {          print("旋转180度");        } else if (180 == rotate_degrees_) {          print("旋转270度");        } else if (270 == rotate_degrees_) {          print("不旋转");        }          await player.setRotation(rotate_degrees_);      }    }  }

经测试,Flutter环境下,RTMP和RTSP播放,拥有Native SDK一样优异的播放体验。