您现在的位置是:首页 > 文章详情

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

日期:2019-04-07点击:397

为什么要用Flutter?

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

Flutter有哪些与众不同

wAAACH5BAEKAAAALAAAAAABAAEAAAICRAEAOw==

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播放器,一个播放器,不是说,有个界面,有个开始、停止按钮就可以了,一个好用的直播播放器,对功能和性能属性要求很高,特别是稳定性和低延迟这块,不谦虚的说,可能是首款功能强大、真正好用的Flutter RTSP/RTMP直播播放SDK


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

1. 视频播放效果:

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

2. 界面截图:

wAAACH5BAEKAAAALAAAAAABAAEAAAICRAEAOw==

上接口:

// // 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); } } } 
wAAACH5BAEKAAAALAAAAAABAAEAAAICRAEAOw==

调用实例:

// // 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_); } } } 
wAAACH5BAEKAAAALAAAAAABAAEAAAICRAEAOw==


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

相关资料:Github: https://github.com/daniulive/SmarterStreaming


 
原文链接:https://yq.aliyun.com/articles/697221
关注公众号

低调大师中文资讯倾力打造互联网数据资讯、行业资源、电子商务、移动互联网、网络营销平台。

持续更新报道IT业界、互联网、市场资讯、驱动更新,是最及时权威的产业资讯及硬件资讯报道平台。

转载内容版权归作者及来源网站所有,本站原创内容转载请注明来源。

文章评论

共有0条评论来说两句吧...

文章二维码

扫描即可查看该文章

点击排行

推荐阅读

最新文章