Flutter下实现低延迟的跨平台RTSP/RTMP播放
为什么要用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外,大牛直播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. 界面截图:
上接口:
//
// 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一样优异的播放体验。
相关资料:Github: https://github.com/daniulive/SmarterStreaming
QQ群: