Flutter下實現低延遲的跨平臺RTSP/RTMP播放

為什么要用Flutter?

Flutter是谷歌的移動UI框架,可以快速在iOS和Android上構建高質量的原生用戶界面。 Flutter可以與現有的代碼一起工作。在全世界,Flutter正在被越來越多的開發者和組織使用,并且Flutter是完全免費、開源的。

Flutter有哪些與眾不同

image

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外,大牛直播SDK為什么要做Flutter下的RTSP/RTMP播放器

首先,Flutter則是依靠Flutter Engine虛擬機在iOS和Android上運行,開發人員可以通過Flutter框架和API在內部進行交互。Flutter Engine使用C/C++編寫,具有低延遲輸入和高幀速率的特點,不像Unity3d一樣,我們是回調YUV/RGB數據,在Unity3d里面繪制,Flutter直接調用native SDK,效率更高。

其次,客戶和開發者驅動,Flutter發展至今,目前還沒有個像樣的RTSP或RTMP播放器,一個播放器,不是說,有個界面,有個開始、停止按鈕就可以了,一個好用的直播播放器,對功能和性能屬性要求很高,特別是穩定性和低延遲這塊,不謙虛的說,可能是首款功能強大、真正好用的Flutter RTSP/RTMP直播播放SDK

以大牛直播SDK的播放器為例:

RTMP直播播放器功能介紹:

  • [支持播放協議]高穩定、超低延遲(一秒內,行業內幾無效果接近的播放端)、業內首屈一指的RTMP直播播放器SDK;
  • [多實例播放]支持多實例播放;
  • [事件回調]支持網絡狀態、buffer狀態等回調;
  • [視頻格式]支持RTMP擴展H.265,H.264;
  • [音頻格式]支持AAC/PCMA/PCMU/Speex;
  • [H.264/H.265軟解碼]支持H.264/H.265軟解;
  • [H.264硬解碼]Android/iOS支持H.264硬解;
  • [H.265硬解]Android/iOS支持H.265硬解;
  • [H.264/H.265硬解碼]Android支持設置Surface模式硬解和普通模式硬解碼;
  • [緩沖時間設置]支持buffer time設置;
  • [首屏秒開]支持首屏秒開模式;
  • [低延遲模式]支持類似于線上娃娃機等直播方案的超低延遲模式設置(公網200~400ms);
  • [復雜網絡處理]支持斷網重連等各種網絡環境自動適配;
  • [快速切換URL]支持播放過程中,快速切換其他URL,內容切換更快;
  • [音視頻多種render機制]Android平臺,視頻:surfaceview/OpenGL ES,音頻:AudioTrack/OpenSL ES;
  • [實時靜音]支持播放過程中,實時靜音/取消靜音;
  • [實時快照]支持播放過程中截取當前播放畫面;
  • [渲染角度]支持0°,90°,180°和270°四個視頻畫面渲染角度設置;
  • [渲染鏡像]支持水平反轉、垂直反轉模式設置;
  • [實時下載速度更新]支持當前下載速度實時回調(支持設置回調時間間隔);
  • [解碼前視頻數據回調]支持H.264/H.265數據回調;
  • [解碼后視頻數據回調]支持解碼后YUV/RGB數據回調;
  • [解碼前音頻數據回調]支持AAC/PCMA/PCMU/SPEEX數據回調;
  • [音視頻自適應]支持播放過程中,音視頻信息改變后自適應;
  • [擴展錄像功能]完美支持和錄像SDK組合使用(支持RTMP擴展H.265流錄制,支持PCMA/PCMU/Speex轉AAC后錄制,支持設置只錄制音頻或視頻)

RTSP直播播放器功能介紹:

  • [支持播放協議]高穩定、超低延遲、業內首屈一指的RTSP直播播放器SDK;
  • [多實例播放]支持多實例播放;
  • [事件回調]支持網絡狀態、buffer狀態等回調;
  • [視頻格式]支持H.265、H.264,此外,Windows/Android平臺還支持RTSP MJPEG播放;
  • [音頻格式]支持AAC/PCMA/PCMU;
  • [H.264/H.265軟解碼]支持H.264/H.265軟解;
  • [H.264硬解碼]Android/iOS支持H.264硬解;
  • [H.265硬解]Android/iOS支持H.265硬解;
  • [H.264/H.265硬解碼]Android支持設置Surface模式硬解和普通模式硬解碼;
  • [RTSP模式設置]支持RTSP TCP/UDP模式設置;
  • [RTSP TCP/UDP自動切換]支持RTSP TCP、UDP模式自動切換;
  • [RTSP超時設置]支持RTSP超時時間設置,單位:秒;
  • [RTSP 401認證處理]支持上報RTSP 401事件,如URL攜帶鑒權信息,會自動處理;
  • [緩沖時間設置]支持buffer time設置;
  • [首屏秒開]支持首屏秒開模式;
  • [復雜網絡處理]支持斷網重連等各種網絡環境自動適配;
  • [快速切換URL]支持播放過程中,快速切換其他URL,內容切換更快;
  • [音視頻多種render機制]Android平臺,視頻:surfaceview/OpenGL ES,音頻:AudioTrack/OpenSL ES;
  • [實時靜音]支持播放過程中,實時靜音/取消靜音;
  • [實時快照]支持播放過程中截取當前播放畫面;
  • [渲染角度]支持0°,90°,180°和270°四個視頻畫面渲染角度設置;
  • [渲染鏡像]支持水平反轉、垂直反轉模式設置;
  • [實時下載速度更新]支持當前下載速度實時回調(支持設置回調時間間隔);
  • [解碼前視頻數據回調]支持H.264/H.265數據回調;
  • [解碼后視頻數據回調]支持解碼后YUV/RGB數據回調;
  • [解碼前音頻數據回調]支持AAC/PCMA/PCMU/SPEEX數據回調;
  • [音視頻自適應]支持播放過程中,音視頻信息改變后自適應;
  • [擴展錄像功能]完美支持和錄像SDK組合使用(支持RTSP H.265流錄制,支持PCMA/PCMU轉AAC后錄制,支持設置只錄制音頻或視頻)

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

1. 視頻播放效果:

http://www.iqiyi.com/w_19s8dv6yht.html

2. 界面截圖:

image

上接口:

//
//  smartplayer.dart
//  smartplayer
//
//  GitHub: https://github.com/daniulive/SmarterStreaming
//  website: http://www.nokunlock.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);
    }
  }
}

image.gif

調用實例:

//
//  main.dart
//  main
//
//  GitHub: https://github.com/daniulive/SmarterStreaming
//  website: http://www.nokunlock.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_);
    }
  }
}

image.gif

經測試,Flutter環境下,RTMP和RTSP播放,擁有Native SDK一樣優異的播放體驗。

相關資料:Github:?https://github.com/daniulive/SmarterStreaming

QQ群:

污污直播app-污污直播破解版永久免费版