提交 2ca131d6 authored 作者: kongdywang's avatar kongdywang

1. add short video demo

2. superPlayer widget Component 3. superPlayer image sprite & video play info 4. superPlayer add loop and mute api 5. global gradle repositories config
上级 892df748
......@@ -8,7 +8,6 @@ rootProject.ext {
在此处可以更换需要的SDK版本,替换为专业版为 com.tencent.liteav:LiteAVSDK_Professional:latest.release
其中 latest.release 可指定为自己需要的版本号,例如 "com.tencent.liteav:LiteAVSDK_Player:9.5.29035" ,写成 latest.release 则默认使用最新版本
*/
// liteavSdk="com.tencent.liteav:LiteAVSDK_Player:latest.release"
liteavSdk="com.tencent.liteav:LiteAVSDK_Player:10.8.0.13052"
liteavSdk="com.tencent.liteav:LiteAVSDK_Player:latest.release"
// liteavSdk="com.tencent.liteav:LiteAVSDK_Professional:latest.release"
}
\ No newline at end of file
pluginManagement {
repositories {
google()
mavenCentral()
}
}
rootProject.name = 'flutter_super_player'
......@@ -11,6 +11,7 @@ import androidx.annotation.NonNull;
import com.tencent.rtmp.ITXVodPlayListener;
import com.tencent.rtmp.TXBitrateItem;
import com.tencent.rtmp.TXImageSprite;
import com.tencent.rtmp.TXLiveConstants;
import com.tencent.rtmp.TXLivePlayer;
import com.tencent.rtmp.TXPlayInfoParams;
......@@ -47,6 +48,7 @@ public class FTXVodPlayer extends FTXBasePlayer implements MethodChannel.MethodC
final private FTXPlayerEventSink mNetStatusSink = new FTXPlayerEventSink();
private TXVodPlayer mVodPlayer;
private TXImageSprite mTxImageSprite;
private static final int Uninitialized = -101;
private TextureRegistry.SurfaceTextureEntry mSurfaceTextureEntry;
......@@ -358,7 +360,29 @@ public class FTXVodPlayer extends FTXBasePlayer implements MethodChannel.MethodC
playForwardAssetPath, getPlayerId());
int pipResult = mPipManager.enterPip(isPlaying(), mPipParams);
result.success(pipResult);
} else if(call.method.equals("initImageSprite")) {
String vvtUrl = call.argument("vvtUrl");
List<String> imageUrls = call.argument("imageUrls");
mTxImageSprite.setVTTUrlAndImageUrls(vvtUrl,imageUrls);
result.success(null);
} else if(call.method.equals("getImageSprite")) {
final Double time = call.argument("time");
new Thread(new Runnable() {
@Override
public void run() {
Bitmap bitmap = mTxImageSprite.getThumbnail(time.floatValue());
ByteArrayOutputStream stream = new ByteArrayOutputStream();
if(null != bitmap) {
bitmap.compress(Bitmap.CompressFormat.JPEG, 100, stream);
byte[] byteArray = stream.toByteArray();
result.success(byteArray);
} else {
result.success(null);
}
}
}).start();
}
else {
result.notImplemented();
}
}
......@@ -368,6 +392,7 @@ public class FTXVodPlayer extends FTXBasePlayer implements MethodChannel.MethodC
mVodPlayer = new TXVodPlayer(mFlutterPluginBinding.getApplicationContext());
mVodPlayer.setVodListener(this);
setPlayer(onlyAudio);
mTxImageSprite = new TXImageSprite(mFlutterPluginBinding.getApplicationContext());
}
return mSurfaceTextureEntry == null ? -1 : mSurfaceTextureEntry.id();
}
......
......@@ -40,12 +40,20 @@ Flutter播放器组件是基于flutter播放器SDK的扩展,播放器组件对
## 集成指引[](id:Guide)
1. 将项目中example下的 `superplayer` 目录复制到自己的flutter工程下
1. 将项目中 `superplayer_widget` 目录复制到自己的flutter工程下
2. 在需要使用到的页面,导入`superplayer`的依赖包,如下所示:
2. 在自己项目的配置文件 `pubspec.yaml` 下添加依赖
```yaml
superplayer_widget:
# 该路径根据superplayer_widget存放路径改变
path: ../superplayer_widget
```
3. 在需要使用到的页面,导入`superplayer_widget`的依赖包,如下所示:
```dart
import 'superplayer/demo_superplayer_lib.dart';
import 'package:superplayer_widget/demo_superplayer_lib.dart';
```
## SDK集成[](id:stepone)
......@@ -163,6 +171,8 @@ SuperPlayerModel model = SuperPlayerModel();
model.appId = 1500005830;
model.videoId = new SuperPlayerVideoId();
model.videoId.fileId = "8602268011437356984";
// psign 即播放器组件签名,签名介绍和生成方式参见链接:https://cloud.tencent.com/document/product/266/42436
model.videoId.pSign = "psignXXX" // 通过fileId播放必须填写
_controller.playWithModelNeedLicence(model);
```
......
......@@ -100,10 +100,10 @@ await _controller.startVodPlay(_url);
:::
::: 通过field方式
```dart
TXPlayerAuthParams authParams = TXPlayerAuthParams();
authParams.appId = 1252463788;
authParams.fileId = "4564972819220421305";
await _controller.startVodPlayWithParams(authParams);
// 通过fileId播放必须填写 psign, psign 即播放器签名,签名介绍和生成方式参见链接:https://cloud.tencent.com/document/product/266/42436
TXPlayInfoParams params = TXPlayInfoParams(appId: 1252463788,
fileId: "4564972819220421305", psign: "psignxxxxxxx");
await _controller.startVodPlayWithParams(params);
```
[媒资管理](https://console.cloud.tencent.com/vod/media) 找到对应的视频文件。在文件名下方可以看到 FileId。
......@@ -581,7 +581,7 @@ bool result = await TXVodDownloadController.instance.deleteDownloadMediaInfo(med
在腾讯云控制台提取到appId, 加密视频的fileId 和 psign后,可以通过下面的方式进行播放:
```dart
// 通过fileId播放必须填写 psign, psign 即播放器签名,签名介绍和生成方式参见链接:https://cloud.tencent.com/document/product/266/42436
TXPlayInfoParams params = TXPlayInfoParams(appId: 1252463788,
fileId: "4564972819220421305", psign: "psignxxxxxxx");
_controller.startVodPlayWithParams(params);
......
......@@ -43,6 +43,11 @@ android {
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
}
debug {
minifyEnabled true // 关闭混淆
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
}
profile {
}
......
......@@ -9,10 +9,10 @@ buildscript {
}
}
allprojects {
rootProject.allprojects {
repositories {
mavenCentral()
google()
mavenCentral()
}
}
......
// Copyright (c) 2022 Tencent. All rights reserved.
import 'package:flutter/cupertino.dart';
import 'package:super_player/super_player.dart';
import 'package:superplayer_widget/demo_superplayer_lib.dart';
import 'shortvideo/demo_short_video_lib.dart';
class DemoShortVideoPlayer extends StatefulWidget {
@override
_DemoShortVideoPlayerState createState() => _DemoShortVideoPlayerState();
}
class _DemoShortVideoPlayerState extends State<DemoShortVideoPlayer> with WidgetsBindingObserver {
static const TAG = "ShortVideo::ShortVideoListViewState";
int _currentIndex = 0;
List<SuperPlayerModel> superPlayerModelList = [];
@override
void initState() {
super.initState();
ShortVideoDataLoader loader = ShortVideoDataLoader();
loader.getPageListDataOneByOneFunction((dataModels) {
setState(() {
superPlayerModelList = dataModels;
});
});
WidgetsBinding.instance.addObserver(this);
}
@override
Widget build(BuildContext context) {
List<Widget> widgetList = [];
for (int i = 0; i < superPlayerModelList.length; i++) {
widgetList.add(ShortVideoPageWidget(
position: i,
videoUrl: superPlayerModelList[i].videoURL,
coverUrl: superPlayerModelList[i].coverUrl));
}
return PageView(
scrollDirection: Axis.vertical,
onPageChanged: (int index) {
LogUtils.i(TAG,"[onPageEndChanged] outside ${_currentIndex.toString()} ——》 ${index.toString()}");
_stopAndPlay(index);
},
children: widgetList,
);
}
@override
void didChangeAppLifecycleState(AppLifecycleState state) {
super.didChangeAppLifecycleState(state);
switch (state) {
case AppLifecycleState.paused:
LogUtils.i(TAG, "[AppLifecycleState.paused]");
_onApplicationPause();
break;
case AppLifecycleState.resumed:
LogUtils.i(TAG, "[AppLifecycleState.resumed]");
_onApplicationResume();
break;
}
}
_stopAndPlay(int index) async {
EventBusUtils.getInstance().fire(new StopAndResumeEvent(index));
_currentIndex = index;
}
_onApplicationPause() {
EventBusUtils.getInstance().fire(new ApplicationPauseEvent());
}
_onApplicationResume() {
EventBusUtils.getInstance().fire(new ApplicationResumeEvent());
}
@override
void dispose() {
WidgetsBinding.instance.removeObserver(this);
super.dispose();
}
}
......@@ -5,8 +5,7 @@ import 'package:flutter/material.dart';
import 'package:flutter_easyloading/flutter_easyloading.dart';
import 'package:super_player/super_player.dart';
import 'package:super_player_example/ui/demo_inputdialog.dart';
import 'superplayer/demo_superplayer_lib.dart';
import 'package:superplayer_widget/demo_superplayer_lib.dart';
/// flutter superplayer demo
class DemoSuperplayer extends StatefulWidget {
......@@ -244,6 +243,14 @@ class _DemoSuperplayerState extends State<DemoSuperplayer> {
model.playAction = playAction;
models.add(model);
model = SuperPlayerModel();
model.appId = 1252463788;
model.videoId = new SuperPlayerVideoId();
model.videoId!.fileId = "5285890781763144364";
model.title = "腾讯云";
model.playAction = playAction;
models.add(model);
model = SuperPlayerModel();
model.appId = 1252463788;
model.videoId = new SuperPlayerVideoId();
......
......@@ -28,7 +28,7 @@ class _DemoTXLivelayerState extends State<DemoTXLivePlayer> with WidgetsBindingO
StreamSubscription? playNetEventSubscription;
StreamSubscription? playerStateEventSubscription;
GlobalKey<VideoSliderState> progressSliderKey = GlobalKey();
GlobalKey<VideoSliderViewState> progressSliderKey = GlobalKey();
Future<void> init() async {
if (!mounted) return;
......@@ -40,7 +40,7 @@ class _DemoTXLivelayerState extends State<DemoTXLivePlayer> with WidgetsBindingO
if (event["event"] == TXVodPlayEvent.PLAY_EVT_PLAY_PROGRESS) {
_progress = event["EVT_PLAY_PROGRESS"].toDouble();
_maxLiveProgressTime = _progress >= _maxLiveProgressTime ? _progress : _maxLiveProgressTime;
progressSliderKey.currentState?.updatePorgess(1, _maxLiveProgressTime);
progressSliderKey.currentState?.updateProgress(1, _maxLiveProgressTime);
} else if (event["event"] == TXVodPlayEvent.PLAY_EVT_PLAY_BEGIN ||
event["event"] == TXVodPlayEvent.PLAY_EVT_RCV_FIRST_I_FRAME) {
//首帧出现
......
......@@ -34,7 +34,7 @@ class _DemoTXVodlayerState extends State<DemoTXVodPlayer>
StreamSubscription? playEventSubscription;
StreamSubscription? playNetEventSubscription;
GlobalKey<VideoSliderState> progressSliderKey = GlobalKey();
GlobalKey<VideoSliderViewState> progressSliderKey = GlobalKey();
Future<void> init() async {
if (!mounted) return;
......@@ -57,9 +57,9 @@ class _DemoTXVodlayerState extends State<DemoTXVodPlayer>
_currentProgress = event[TXVodPlayEvent.EVT_PLAY_PROGRESS].toDouble();
double videoDuration = event[TXVodPlayEvent.EVT_PLAY_DURATION].toDouble(); // 总播放时长,转换后的单位 秒
if (videoDuration == 0.0) {
progressSliderKey.currentState?.updatePorgess(0.0, 0.0);
progressSliderKey.currentState?.updateProgress(0.0, 0.0);
} else {
progressSliderKey.currentState?.updatePorgess(_currentProgress / videoDuration, videoDuration);
progressSliderKey.currentState?.updateProgress(_currentProgress / videoDuration, videoDuration);
}
} else if (event["event"] == TXVodPlayEvent.PLAY_EVT_GET_PLAYINFO_SUCC) {
String? playUrl = event[TXVodPlayEvent.EVT_PLAY_URL]?.toString();
......
......@@ -5,6 +5,7 @@ import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:flutter_easyloading/flutter_easyloading.dart';
import 'package:super_player/super_player.dart';
import 'package:super_player_example/shortvideo/demo_short_video_lib.dart';
import 'ui/treePage.dart';
......@@ -106,4 +107,10 @@ class _MyAppState extends State<MyApp> {
builder: EasyLoading.init(),
);
}
@override
void dispose() {
super.dispose();
EventBusUtils.getInstance().destroy();
}
}
// Copyright (c) 2022 Tencent. All rights reserved.
library demo_short_video_player_lib;
import 'dart:async';
import 'package:flutter/material.dart';
import 'package:super_player/super_player.dart';
import 'package:superplayer_widget/demo_superplayer_lib.dart';
import 'package:super_player_example/ui/demo_video_slider_view.dart';
import 'package:event_bus/event_bus.dart';
part 'model/short_video_data_loader.dart';
part 'short_video_page_widget.dart';
part 'tools/event_bus_utils.dart';
\ No newline at end of file
// Copyright (c) 2022 Tencent. All rights reserved.
part of demo_short_video_player_lib;
class ShortVideoDataLoader {
List<SuperPlayerModel> _currentModels = [];
int _appId = 1500005830;
var _fileIdArray = [
"387702294394366256",
"387702294394228858",
"387702294394228636",
"387702294394228527",
"387702294167066523",
"387702294167066515",
"387702294168748446",
"387702294394227941"
];
List<SuperPlayerModel> _defaultData = [];
SuperVodDataLoader _loader = SuperVodDataLoader();
getPageListDataOneByOneFunction(
Function(List<SuperPlayerModel> model) callback) {
_currentModels.clear();
for (int i = 0; i < _fileIdArray.length; i++) {
SuperPlayerModel model = new SuperPlayerModel();
model.appId = _appId;
model.videoId = new SuperPlayerVideoId();
model.videoId?.fileId = _fileIdArray[i];
_defaultData.add(model);
}
for (var model in _defaultData) {
_getVodListData(model, callback);
}
}
_getVodListData(SuperPlayerModel model, Function(List<SuperPlayerModel> models) callback) async {
_loader.getVideoData(model, (resultModel) {
_currentModels.add(resultModel);
if (_currentModels.length == _defaultData.length) {
callback(_currentModels);
}
});
}
}
// Copyright (c) 2022 Tencent. All rights reserved.
part of demo_short_video_player_lib;
class ShortVideoPageWidget extends StatefulWidget {
String videoUrl;
String coverUrl;
int position;
late TXVodPlayerController _controller;
ShortVideoPageWidget(
{required this.position,
required this.videoUrl,
required this.coverUrl});
@override
State<StatefulWidget> createState() {
return _TXVodPlayerPageState();
}
}
class _TXVodPlayerPageState extends State<ShortVideoPageWidget> {
static const TAG = "ShortVideo::TXVodPlayerPageState";
late TXPlayerVideo _txPlayerVideo;
bool _isVideoPrepared = false;
bool _isVideoPlaying = true;
GlobalKey<VideoSliderViewState> _progressSliderKey = GlobalKey();
late StreamSubscription _streamSubscriptionStopAndPlay;
late StreamSubscription _streamSubscriptionApplicationResume;
late StreamSubscription _streamSubscriptionApplicationPause;
@override
void initState() {
super.initState();
_init();
}
_init() async {
widget._controller = new TXVodPlayerController();
_txPlayerVideo = new TXPlayerVideo(controller: widget._controller);
await widget._controller.initialize();
widget._controller.setConfig(FTXVodPlayConfig());
LogUtils.i(TAG, " [init] ${widget.position.toString()} ${this.hashCode.toString()} ${widget._controller.hashCode.toString()}");
_setPlayerListener();
_setEventBusListener();
if (widget.position == 0) {
_startPlay();
}
}
@override
Widget build(BuildContext context) {
return _getTXVodPlayerMainPage();
}
@override
void dispose() {
_dispose();
super.dispose();
}
_dispose() async {
_streamSubscriptionStopAndPlay.cancel();
_streamSubscriptionApplicationResume.cancel();
_streamSubscriptionApplicationPause.cancel();
await _stop();
widget._controller.dispose();
LogUtils.i(TAG, " [dispose] ${widget.position.toString()} ${this.hashCode.toString()} ${widget._controller.hashCode.toString()}");
}
Widget _getTXVodPlayerMainPage() {
return Stack(
children: <Widget>[
_getGestureDetectorView(),
_getPreviewImg(),
_getSeekBarView()
],
);
}
GestureDetector _getGestureDetectorView() {
return GestureDetector(
child: Stack(
children: <Widget>[
Container(
child: _txPlayerVideo,
),
_getPauseView()
],
),
onTap: () {
_onTapPageView();
});
}
_onTapPageView() {
widget._controller.isPlaying().then((value) {
value == true ? _pause() :_resume();
});
LogUtils.i(TAG, "tap ${_isVideoPlaying.toString()}");
}
Widget _getSeekBarView() {
return Positioned(
child: VideoSliderView(widget._controller, _progressSliderKey),
bottom: 20,
right: 0,
left: 0,
);
}
Widget _getPauseView() {
return Offstage(
offstage: _isVideoPlaying,
child: Align(
child: Container(
child: Image(image: AssetImage("images/superplayer_ic_vod_play_normal.png", package:StringResource.PKG_NAME)),
height: 50,
width: 50),
alignment: Alignment.center,
));
}
Widget _getPreviewImg() {
return Offstage(
offstage: _isVideoPrepared,
child: Container(
decoration: BoxDecoration(
color: Colors.black,
image: DecorationImage(
image: NetworkImage(widget.coverUrl),
fit: BoxFit.fill,
)),
child: Scaffold(
backgroundColor: Colors.transparent, //把scaffold的背景色改成透明
)));
}
_pause() {
LogUtils.i(TAG, "[_pause]");
widget._controller.pause();
setState(() {
_isVideoPlaying = false;
});
}
_resume() {
LogUtils.i(TAG, "[_resume]");
widget._controller.resume();
setState(() {
_isVideoPlaying = true;
});
}
_stopLastAndPlayCurrent(StopAndResumeEvent event) {
LogUtils.i(TAG, " [received at not current outside] ${widget.position.toString()} ${this.hashCode.toString()} ${widget.hashCode.toString()} ${widget._controller.hashCode.toString()}");
event.index != widget.position ? _stop() :_startPlay();
}
Future<void> _stop() async{
if (!mounted) return;
LogUtils.i(TAG, " [stop] ${widget.position.toString()} ${widget.hashCode.toString()}");
_isVideoPrepared = false;
_isVideoPlaying = true;
widget._controller.stop();
}
_startPlay() async {
LogUtils.i(TAG, " [_startPlay]");
setState(() {
_isVideoPlaying = true;
});
await widget._controller.setLoop(true);
widget._controller.startVodPlay(widget.videoUrl);
}
_hideCover() {
if (!mounted) return;
LogUtils.i(TAG, " [received] ${widget.position.toString()} ${widget.hashCode.toString()}");
setState(() {
_isVideoPrepared = true;
});
}
_setEventBusListener() {
_streamSubscriptionStopAndPlay = EventBusUtils.getInstance().on<StopAndResumeEvent>().listen((event) {
_stopLastAndPlayCurrent(event);
});
_streamSubscriptionApplicationResume = EventBusUtils.getInstance().on<ApplicationResumeEvent>().listen((event) {
_resume();
});
_streamSubscriptionApplicationPause = EventBusUtils.getInstance().on<ApplicationPauseEvent>().listen((event) {
_pause();
});
}
_setPlayerListener() {
widget._controller.onPlayerEventBroadcast.listen((event) async {
if (event["event"] == TXVodPlayEvent.PLAY_EVT_PLAY_PROGRESS) {
if (!mounted) return;
double currentProgress = event["EVT_PLAY_PROGRESS"].toDouble();
double videoDuration = event["EVT_PLAY_DURATION"].toDouble(); // 总播放时长,转换后的单位 秒
_progressSliderKey.currentState?.updateProgress(currentProgress / videoDuration, videoDuration);
} else if (event["event"] == TXVodPlayEvent.PLAY_EVT_RCV_FIRST_I_FRAME) {
LogUtils.i(TAG, " [received] TXVodPlayEvent.PLAY_EVT_RCV_FIRST_I_FRAME");
_hideCover();
}
});
}
}
// Copyright (c) 2022 Tencent. All rights reserved.
part of demo_short_video_player_lib;
class EventBusUtils {
static final EventBus _instance = EventBus();
static EventBus getInstance() {
return _instance;
}
}
class ApplicationPauseEvent{
ApplicationPauseEvent();
}
class StopAndResumeEvent{
int index;
StopAndResumeEvent(this.index);
}
class ApplicationResumeEvent{
ApplicationResumeEvent();
}
\ No newline at end of file
......@@ -11,16 +11,16 @@ class VideoSliderView extends StatefulWidget {
@override
State<StatefulWidget> createState() {
return VideoSliderState();
return VideoSliderViewState();
}
}
class VideoSliderState extends State<VideoSliderView> {
class VideoSliderViewState extends State<VideoSliderView> {
double _currentProgress = 0.0;
double _videoDuration = 0.0;
bool isSliding = false;
bool _isSliding = false;
@override
Widget build(BuildContext context) {
......@@ -40,14 +40,14 @@ class VideoSliderState extends State<VideoSliderView> {
max: 1,
value: _currentProgress,
onChanged: (double value) {
isSliding = true;
_isSliding = true;
setState(() {
_currentProgress = value;
});
},
onChangeEnd: (double value) {
setState(() {
isSliding = false;
_isSliding = false;
_currentProgress = value;
if (widget._controller is TXVodPlayerController) {
TXVodPlayerController controller = widget._controller as TXVodPlayerController;
......@@ -64,8 +64,8 @@ class VideoSliderState extends State<VideoSliderView> {
));
}
void updatePorgess(double progress,double totalDuration) {
if(!isSliding) {
void updateProgress(double progress,double totalDuration) {
if(!_isSliding) {
setState(() {
if(progress > 1.0) {
progress = 1.0;
......
......@@ -6,6 +6,7 @@ import 'package:super_player_example/demo_superplayer.dart';
import 'package:super_player_example/demo_txLiveplayer.dart';
import 'package:super_player_example/demo_txvodplayer.dart';
import '../demo_short_video_player.dart';
import 'demo_define.dart';
class TreePage extends StatefulWidget {
......@@ -43,6 +44,7 @@ class _TreePageState extends State<TreePage> {
TreeDatachild("直播播放"),
TreeDatachild("点播播放"),
TreeDatachild("播放器组件"),
TreeDatachild("短视频播放")
], "播放器", false),
];
}
......@@ -84,7 +86,7 @@ class _TreePageState extends State<TreePage> {
},
//展开内容
body: Container(
height: 200,
height: 250,
padding: EdgeInsets.symmetric(horizontal: 5.0),
child: ListView.builder(
itemCount: treeData.children.length,
......@@ -133,8 +135,10 @@ class _TreePageState extends State<TreePage> {
return DemoTXLivePlayer();
}else if (i == 1) {
return DemoTXVodPlayer();
} else {
} else if (i==2){
return DemoSuperplayer();
} else {
return DemoShortVideoPlayer();
}
}
),
......
......@@ -12,6 +12,7 @@ environment:
dependencies:
flutter:
sdk: flutter
event_bus: ^2.0.0
auto_orientation: ^2.2.1
......@@ -24,6 +25,9 @@ dependencies:
# The example app is bundled with the plugin so we use a path dependency on
# the parent directory to use the current plugin's version.
path: ../
superplayer_widget:
# 该路径根据superplayer_widget存放路径改变
path: ../superplayer_widget
# The following adds the Cupertino Icons font to your application.
# Use with the CupertinoIcons class for iOS style icons.
......@@ -44,22 +48,6 @@ flutter:
# the material Icons class.
uses-material-design: true
assets:
- images/ic_pip_play_icon.png
- images/ic_pip_play_normal.png
- images/ic_pip_play_pause.png
- images/ic_pip_play_forward.png
- images/ic_pip_play_replay.png
- images/superplayer_ic_vod_more_normal.png
- images/superplayer_ic_light_max.png
- images/superplayer_ic_light_min.png
- images/superplayer_ic_volume_max.png
- images/superplayer_ic_volume_min.png
- images/superplayer_ic_vod_pause_normal.png
- images/superplayer_ic_vod_play_normal.png
- images/superplayer_bottom_shadow.png
- images/superplayer_ic_vod_fullscreen.png
- images/superplayer_top_shadow.png
- images/superplayer_btn_back_play.png
- images/ic_new_vod_bg.png
- images/addp.png
# To add assets to your application, add an assets section, like this:
......
......@@ -25,6 +25,7 @@ static const int CODE_ON_RECEIVE_FIRST_FRAME = 2003;
*/
@implementation FTXVodPlayer {
TXVodPlayer *_txVodPlayer;
TXImageSprite *_txImageSprite;
FTXPlayerEventSinkQueue *_eventSink;
FTXPlayerEventSinkQueue *_netStatusSink;
FlutterMethodChannel *_methodChannel;
......@@ -149,7 +150,7 @@ BOOL volatile isStop = false;
_txVodPlayer.vodDelegate = self;
[self setupPlayerWithBool:onlyAudio];
}
_txImageSprite = [[TXImageSprite alloc] init];
return [NSNumber numberWithLongLong:_textureId];
}
......@@ -427,6 +428,32 @@ BOOL volatile isStop = false;
int ret = [self enterPictureInPictureMode];
result(@(ret));
}
else if ([@"initImageSprite" isEqualToString:call.method]) {
NSDictionary *args = call.arguments;
NSString *vvtStr = args[@"vvtUrl"];
NSArray *imageStrs = args[@"imageUrls"];
NSMutableArray *imageUrls = @[].mutableCopy;
for(NSString *url in imageStrs) {
NSURL *nsurl = [NSURL URLWithString:url];
if (nsurl) {
[imageUrls addObject:nsurl];
}
}
[_txImageSprite setVTTUrl: [NSURL URLWithString:vvtStr] imageUrls:imageUrls];
result(nil);
}
else if ([@"getImageSprite" isEqualToString:call.method]) {
NSDictionary *args = call.arguments;
NSInteger time = [args[@"time"] intValue];
UIImage *imageSprite = [_txImageSprite getThumbnail:time];
if(nil != imageSprite) {
NSData *data = UIImagePNGRepresentation(imageSprite);
result(data);
} else {
result(nil);
}
}
else {
result(FlutterMethodNotImplemented);
}
......
......@@ -21,6 +21,7 @@ class TXPlayerVideoState extends State<TXPlayerVideo> {
@override
void initState() {
super.initState();
controller = widget.controller;
_checkStreamListen();
_resetControllerLink();
......
......@@ -402,6 +402,17 @@ class TXVodPlayerController extends ChangeNotifier implements ValueListenable<TX
}
}
Future<void> initImageSprite(String vvtUrl, List<String> imageUrls) async {
if (_isNeedDisposed) return;
await _initPlayer.future;
await _channel.invokeMethod("initImageSprite", {"vvtUrl":vvtUrl, "imageUrls":imageUrls});
}
Future<Uint8List?> getImageSprite(double time) async {
await _initPlayer.future;
return await _channel.invokeMethod("getImageSprite",{"time":time});
}
/// 获取总时长
Future<double> getDuration() async {
if (_isNeedDisposed) return 0;
......
......@@ -5,6 +5,7 @@ import 'dart:async';
import 'dart:core';
import 'dart:io';
import 'dart:math';
import 'dart:typed_data';
import 'package:flutter/cupertino.dart';
import 'package:flutter/foundation.dart';
......
......@@ -20,4 +20,6 @@ class StringResource {
static const OPEN_PIP = "正在开启画中画";
static const CLOSE_PIP = "正在关闭画中画";
static const ERROR_PIP = "画中画开启失败";
static const PKG_NAME = "superplayer_widget";
}
// Copyright (c) 2022 Tencent. All rights reserved.
part of demo_super_player_lib;
typedef TaskCallback = void Function(bool success, dynamic result);
typedef TaskDetailFunc = Future Function();
/// 确保入栈的异步/非异步按顺序执行
/// 单线程模型
class TaskExecutors {
bool _isTaskRunning = false;
final LinkedList<TaskItem> _taskList = LinkedList<TaskItem>();
Future addTask(TaskDetailFunc task) {
Completer completer = Completer();
TaskItem taskItem = TaskItem(task, (success, result) {
if (success) {
completer.complete(result);
} else {
completer.completeError(result);
}
_isTaskRunning = false;
//递归任务
_doTask();
});
_taskList.add(taskItem);
_doTask();
return completer.future;
}
Future<void> _doTask() async {
if(_isTaskRunning) return;
if (_taskList.isEmpty) return;
_isTaskRunning = true;
TaskItem taskItem = _taskList.first;
_taskList.remove(taskItem);
try{
var result = await taskItem.function.call();
taskItem.taskCallback.call(true, result);
} catch(_){
taskItem.taskCallback.call(false, _.toString());
}
}
}
class TaskItem extends LinkedListEntry<TaskItem> {
TaskDetailFunc function;
TaskCallback taskCallback;
TaskItem(this.function, this.taskCallback);
}
......@@ -2,10 +2,12 @@
library demo_super_player_lib;
import 'dart:async';
import 'dart:collection';
import 'dart:convert';
import 'dart:core';
import 'dart:io';
import 'dart:math';
import 'dart:typed_data';
import 'package:auto_orientation/auto_orientation.dart';
import 'package:flutter/material.dart';
......@@ -24,6 +26,7 @@ part 'cgi/super_vod_data_loader.dart';
part 'model/superplayer_define.dart';
part 'model/superplayer_model.dart';
part 'tools/video_quality_utils.dart';
part 'tools/utils.dart';
part 'ui/superplayer_bottom_view.dart';
part 'ui/superplayer_quality_view.dart';
part 'ui/superplayer_title_view.dart';
......@@ -34,3 +37,4 @@ part 'ui/superplayer_video_slider.dart';
part 'common/color_resource.dart';
part 'common/string_resource.dart';
part 'common/theme_resource.dart';
part 'common/task_executors.dart';
\ No newline at end of file
......@@ -141,3 +141,9 @@ class SuperPlayerVideoId {
String fileId = ""; // 腾讯云视频fileId
String psign = ""; // v4 开启防盗链必填
}
/// 进度条打点信息
class SliderPoint {
Color pointColor = Colors.white;
double progress = 0;
}
......@@ -33,9 +33,14 @@ class SuperPlayerController {
StreamSubscription? _livePlayEventListener;
StreamSubscription? _liveNetEventListener;
PlayImageSpriteInfo? spriteInfo;
List<PlayKeyFrameDescInfo>? keyFrameInfo;
String _currentPlayUrl = "";
bool isPrepared = false;
bool isMute = false;
bool isLoop = false;
bool _needToResume = false;
bool _needToPause = false;
bool _isMultiBitrateStream = false; // 是否是多码流url播放
......@@ -55,6 +60,10 @@ class SuperPlayerController {
double videoHeight = 0;
double currentPlayRate = 1.0;
// 规避IOS部分情况下收不到FirstFrame事件
bool _isCalledFirstFrame = false;
bool _isRecFirstFrameEvent = false;
SuperPlayerController(this._context) {
_initVodPlayer();
_initLivePlayer();
......@@ -78,8 +87,20 @@ class SuperPlayerController {
_vodPlayEventListener = _vodPlayerController.onPlayerEventBroadcast.listen((event) async {
int eventCode = event['event'];
switch (eventCode) {
case TXVodPlayEvent.PLAY_EVT_GET_PLAYINFO_SUCC:
_currentPlayUrl = event[TXVodPlayEvent.EVT_PLAY_URL];
PlayImageSpriteInfo playImageSpriteInfo = PlayImageSpriteInfo();
playImageSpriteInfo.webVttUrl = event[TXVodPlayEvent.EVT_IMAGESPRIT_WEBVTTURL] ?? "";
event[TXVodPlayEvent.EVT_IMAGESPRIT_IMAGEURL_LIST]?.forEach((element) {
playImageSpriteInfo.imageUrls.add(element);
});
_vodPlayerController.initImageSprite(playImageSpriteInfo.webVttUrl, playImageSpriteInfo.imageUrls);
spriteInfo = playImageSpriteInfo;
break;
case TXVodPlayEvent.PLAY_EVT_VOD_PLAY_PREPARED: // vodPrepared
isPrepared = true;
_isRecFirstFrameEvent = false;
_isCalledFirstFrame = false;
if (_isMultiBitrateStream) {
List<dynamic>? bitrateListTemp = await _vodPlayerController.getSupportedBitrates();
List<FTXBitrateItem> bitrateList = [];
......@@ -135,14 +156,14 @@ class SuperPlayerController {
if (_needToPause) {
return;
}
_isRecFirstFrameEvent = true;
if (_changeHWAcceleration) {
LogUtils.d(TAG, "seek pos $_seekPos");
seek(_seekPos);
_changeHWAcceleration = false;
}
_updatePlayerState(SuperPlayerState.PLAYING);
_observer?.onRcvFirstIframe();
_onRecFirstFrame();
break;
case TXVodPlayEvent.PLAY_EVT_PLAY_END:
_updatePlayerState(SuperPlayerState.END);
......@@ -159,6 +180,13 @@ class SuperPlayerController {
if (videoDuration != 0) {
_observer?.onPlayProgress(currentDuration, videoDuration, await getPlayableDuration());
}
if (!_isCalledFirstFrame) {
if (!_isRecFirstFrameEvent) {
_isRecFirstFrameEvent = true;
} else {
_onRecFirstFrame();
}
}
break;
}
});
......@@ -265,12 +293,13 @@ class SuperPlayerController {
Future<void> _playWithModelInner(SuperPlayerModel videoModel) async {
this.videoModel = videoModel;
_playAction = videoModel.playAction;
_observer?.onVideoImageSpriteAndKeyFrameChanged(null, null);
_updateImageSpriteAndKeyFrame(null, null);
_currentProtocol = null;
// 优先使用url播放
if (videoModel.videoURL.isNotEmpty) {
_playWithUrl(videoModel);
getInfo(videoModel);
} else if (videoModel.videoId != null && (videoModel.videoId!.fileId.isNotEmpty)) {
_currentProtocol = PlayInfoProtocol(videoModel);
// 没有url的时候,根据field去请求
......@@ -278,6 +307,13 @@ class SuperPlayerController {
}
}
void getInfo(SuperPlayerModel videoModel) {
PlayInfoProtocol temp = PlayInfoProtocol(videoModel);
temp.sendRequest((protocol, resultModel) async {
_updateImageSpriteAndKeyFrame(protocol.getImageSpriteInfo(), protocol.getKeyFrameDescInfo());
}, (errCode, errorMsg) {});
}
Future<void> _sendRequest() async {
_currentProtocol?.sendRequest((protocol, resultModel) async {
// onSuccess
......@@ -287,7 +323,7 @@ class SuperPlayerController {
_playModeVideo(protocol);
_updatePlayerType(SuperPlayerType.VOD);
_observer?.onPlayProgress(0, resultModel.duration.toDouble(), await getPlayableDuration());
_observer?.onVideoImageSpriteAndKeyFrameChanged(protocol.getImageSpriteInfo(), protocol.getKeyFrameDescInfo());
_updateImageSpriteAndKeyFrame(protocol.getImageSpriteInfo(), protocol.getKeyFrameDescInfo());
}, (errCode, message) {
// onError
_observer?.onError(SuperPlayerCode.VOD_REQUEST_FILE_ID_FAIL, "播放视频文件失败 code = $errCode msg = $message");
......@@ -295,6 +331,15 @@ class SuperPlayerController {
});
}
void _updateImageSpriteAndKeyFrame(PlayImageSpriteInfo? spriteInfo, List<PlayKeyFrameDescInfo>? keyFrameInfo) {
_observer?.onVideoImageSpriteAndKeyFrameChanged(spriteInfo, keyFrameInfo);
if (null != spriteInfo) {
_vodPlayerController.initImageSprite(spriteInfo.webVttUrl, spriteInfo.imageUrls);
}
this.spriteInfo = spriteInfo;
this.keyFrameInfo = keyFrameInfo;
}
Future<double> getPlayableDuration() async {
return await _vodPlayerController.getPlayableDuration();
}
......@@ -304,9 +349,8 @@ class SuperPlayerController {
_playVodUrl(videoUrl);
List<VideoQuality>? qualityList = protocol.getVideoQualityList();
_isMultiBitrateStream = protocol.getResolutionNameList() != null ||
qualityList != null ||
(videoUrl != null && videoUrl.contains("m3u8"));
_isMultiBitrateStream =
protocol.getResolutionNameList() != null || qualityList != null || (videoUrl != null && videoUrl.contains("m3u8"));
_updateVideoQualityList(qualityList, protocol.getDefaultVideoQuality());
}
......@@ -372,8 +416,7 @@ class SuperPlayerController {
if (_playAction == SuperPlayerModel.PLAY_ACTION_PRELOAD) {
await _vodPlayerController.setAutoPlay(isAutoPlay: false);
_playAction = SuperPlayerModel.PLAY_ACTION_AUTO_PLAY;
} else if (_playAction == SuperPlayerModel.PLAY_ACTION_AUTO_PLAY ||
_playAction == SuperPlayerModel.PLAY_ACTION_MANUAL_PLAY) {
} else if (_playAction == SuperPlayerModel.PLAY_ACTION_AUTO_PLAY || _playAction == SuperPlayerModel.PLAY_ACTION_MANUAL_PLAY) {
await _vodPlayerController.setAutoPlay(isAutoPlay: true);
}
_setVodListener();
......@@ -393,7 +436,7 @@ class SuperPlayerController {
if (query == null || query.isEmpty) {
query = "";
} else {
query = query + "&";
query = "$query&";
if (query.contains("spfileid") || query.contains("spdrmtype") || query.contains("spappid")) {
LogUtils.d(TAG, "url contains superplay key. $query");
}
......@@ -408,6 +451,13 @@ class SuperPlayerController {
}
}
void _onRecFirstFrame() {
if (_isRecFirstFrameEvent) {
_isCalledFirstFrame = true;
_observer?.onRcvFirstIframe();
}
}
/// 暂停视频
/// 涉及到_updatePlayerState相关的方法,不使用异步,避免异步调用导致的playerState更新不及时
void pause() {
......@@ -536,9 +586,7 @@ class SuperPlayerController {
String title = "";
if (videoModel != null && null != videoModel!.title && videoModel!.title.isNotEmpty) {
title = videoModel!.title;
} else if (_currentProtocol != null &&
null != _currentProtocol!.getName() &&
_currentProtocol!.getName()!.isNotEmpty) {
} else if (_currentProtocol != null && null != _currentProtocol!.getName() && _currentProtocol!.getName()!.isNotEmpty) {
title = _currentProtocol!.getName()!;
}
return title;
......@@ -551,7 +599,7 @@ class SuperPlayerController {
}
void _addSimpleEvent(String event) {
Map<String, String> eventMap = new Map();
Map<String, String> eventMap = {};
eventMap['event'] = event;
_simpleEventStreamController.add(eventMap);
}
......@@ -560,7 +608,7 @@ class SuperPlayerController {
if (_playerUIStatus != status) {
if (status == SuperPlayerUIStatus.FULLSCREEN_MODE) {
_addSimpleEvent(SuperPlayerViewEvent.onStartFullScreenPlay);
} else if(_playerUIStatus == SuperPlayerUIStatus.FULLSCREEN_MODE) {
} else if (_playerUIStatus == SuperPlayerUIStatus.FULLSCREEN_MODE) {
_addSimpleEvent(SuperPlayerViewEvent.onStopFullScreenPlay);
}
_playerUIStatus = status;
......@@ -574,8 +622,7 @@ class SuperPlayerController {
/// 是否是HTTP-FLV协议
bool _isFLVPlay(String? videoURL) {
return (null != videoURL && videoURL.startsWith("http://") || videoURL!.startsWith("https://")) &&
videoURL.contains(".flv");
return (null != videoURL && videoURL.startsWith("http://") || videoURL!.startsWith("https://")) && videoURL.contains(".flv");
}
/// 重置播放器状态
......@@ -693,21 +740,14 @@ class SuperPlayerController {
/// </h1>
/// @param backIcon playIcon pauseIcon forwardIcon 为播放后退、播放、暂停、前进的图标,如果赋值的话,将会使用传递的图标,否则
/// 使用系统默认图标,只支持flutter本地资源图片,传递的时候,与flutter使用图片资源一致,例如: images/back_icon.png
Future<int> enterPictureInPictureMode(
{String? backIcon, String? playIcon, String? pauseIcon, String? forwardIcon}) async {
Future<int> enterPictureInPictureMode({String? backIcon, String? playIcon, String? pauseIcon, String? forwardIcon}) async {
if (_playerUIStatus == SuperPlayerUIStatus.WINDOW_MODE) {
if (playerType == SuperPlayerType.VOD) {
return _vodPlayerController.enterPictureInPictureMode(
backIconForAndroid: backIcon,
playIconForAndroid: playIcon,
pauseIconForAndroid: pauseIcon,
forwardIconForAndroid: forwardIcon);
backIconForAndroid: backIcon, playIconForAndroid: playIcon, pauseIconForAndroid: pauseIcon, forwardIconForAndroid: forwardIcon);
} else {
return _livePlayerController.enterPictureInPictureMode(
backIconForAndroid: backIcon,
playIconForAndroid: playIcon,
pauseIconForAndroid: pauseIcon,
forwardIconForAndroid: forwardIcon);
backIconForAndroid: backIcon, playIconForAndroid: playIcon, pauseIconForAndroid: pauseIcon, forwardIconForAndroid: forwardIcon);
}
}
return TXVodPlayEvent.ERROR_PIP_CAN_NOT_ENTER;
......@@ -736,6 +776,24 @@ class SuperPlayerController {
}
}
/// 设置是否静音
Future<void> setMute(bool mute) async {
isMute = mute;
if (playerType == SuperPlayerType.VOD) {
return await _vodPlayerController.setMute(mute);
} else {
return await _livePlayerController.setMute(mute);
}
}
/// 设置是否循环播放,不支持直播时调用
Future<void> setLoop(bool loop) async {
if (playerType == SuperPlayerType.VOD) {
isLoop = loop;
return await _vodPlayerController.setLoop(loop);
}
}
Future<void> setPlayRate(double rate) async {
currentPlayRate = rate;
_vodPlayerController.setRate(rate);
......
// Copyright (c) 2022 Tencent. All rights reserved.
part of demo_super_player_lib;
class Utils {
static bool compareBuffer(Uint8List? buffer1, Uint8List? buffer2) {
if(buffer1 == null || buffer2 == null) {
return false;
}
if (buffer1.length != buffer2.length) {
return false;
}
for (int i = 0; i < buffer1.length; i++) {
if (buffer1[i] != buffer2[i]) {
return false;
}
}
return true;
}
/// 将秒数转换为hh:mm:ss的格式
static String formattedTime(double second) {
int h = second ~/ 3600;
int m = (second % 3600) ~/ 60;
int s = ((second % 3600) % 60).toInt();
String formatTime;
if (h == 0) {
formatTime = "${asTwoDigit(m)}:${asTwoDigit(s)}";
} else {
formatTime = "${asTwoDigit(h)}:${asTwoDigit(m)}:${asTwoDigit(s)}";
}
return formatTime;
}
static String asTwoDigit(int digit) {
String value = "";
if (digit < 10) {
value = "0";
}
value += digit.toString();
return value;
}
}
\ No newline at end of file
......@@ -4,9 +4,9 @@ part of demo_super_player_lib;
/// slider
class VideoBottomView extends StatefulWidget {
final SuperPlayerController _playerController;
final _BottomViewController _controller;
final BottomViewController _controller;
VideoBottomView(this._playerController, this._controller, GlobalKey<_VideoBottomViewState> key) : super(key: key);
const VideoBottomView(this._playerController, this._controller, Key key) : super(key: key);
@override
State<StatefulWidget> createState() {
......@@ -26,6 +26,10 @@ class _VideoBottomViewState extends State<VideoBottomView> {
bool _isOnDraging = false;
SuperPlayerType _playerType = SuperPlayerType.VOD;
VideoQuality? _currentQuality;
final GlobalKey<VideoSliderState> _sliderView = GlobalKey();
final List<SliderPoint> _playPoints = [];
List<PlayKeyFrameDescInfo>? keyFrameList;
PlayKeyFrameDescInfo? showedKeyFrameInfo;
@override
void initState() {
......@@ -45,6 +49,8 @@ class _VideoBottomViewState extends State<VideoBottomView> {
_isShowQuality = isFullScreen;
_currentQuality = widget._playerController.currentQuality;
_playerType = widget._playerController.playerType;
showedKeyFrameInfo = null;
setKeyFrame(widget._playerController.keyFrameInfo);
super.initState();
}
......@@ -55,26 +61,47 @@ class _VideoBottomViewState extends State<VideoBottomView> {
bottom: topBottomOffset,
right: 0,
left: 0,
child: Container(
decoration: BoxDecoration(
image: DecorationImage(image: AssetImage("images/superplayer_bottom_shadow.png"), fit: BoxFit.fill)),
padding: EdgeInsets.only(left: 6, right: 6),
child: Column(
children: [
Center(
child: Visibility(
visible: null != showedKeyFrameInfo,
child: null != showedKeyFrameInfo
? Container(
decoration: const BoxDecoration(
color: Color(ColorResource.COLOR_TRANS_BLACK), borderRadius: BorderRadius.all(Radius.circular(50))),
padding: const EdgeInsets.only(left: 10, right: 10, top: 5, bottom: 5),
child: Text(
"${Utils.formattedTime(showedKeyFrameInfo!.time)} ${showedKeyFrameInfo!.content}",
style: const TextStyle(color: Colors.white, fontSize: 14),
softWrap: true,
),
)
: const SizedBox()),
),
Container(
decoration: const BoxDecoration(
image: DecorationImage(
image: AssetImage("images/superplayer_bottom_shadow.png", package: StringResource.PKG_NAME), fit: BoxFit.fill)),
padding: const EdgeInsets.only(left: 6, right: 6),
child: Row(
children: [
_getPlayImage(),
Text(
_buildTextString(_currentDuration),
style: TextStyle(fontSize: 12, color: Colors.white),
style: const TextStyle(fontSize: 12, color: Colors.white),
),
_getSlider(),
Text(
_buildTextString(_videoDuration),
style: TextStyle(fontSize: 12, color: Colors.white),
style: const TextStyle(fontSize: 12, color: Colors.white),
),
_getFullScreenImage(),
_getQualityButton(),
],
),
)
],
),
);
}
......@@ -85,11 +112,11 @@ class _VideoBottomViewState extends State<VideoBottomView> {
child: InkWell(
onTap: onTapQualityView,
child: Container(
padding: EdgeInsets.only(left: 5),
padding: const EdgeInsets.only(left: 5),
child: _currentQuality != null
? Text(
VideoQualityUtils.transformToQualityName(_currentQuality!.title),
style: TextStyle(fontSize: 12, color: Colors.white),
style: const TextStyle(fontSize: 12, color: Colors.white),
)
: Container(),
)),
......@@ -100,15 +127,15 @@ class _VideoBottomViewState extends State<VideoBottomView> {
return InkWell(
onTap: onTapStartOrPause,
child: _isPlayMode
? Image(
? const Image(
width: 30,
height: 30,
image: AssetImage("images/superplayer_ic_vod_pause_normal.png"),
image: AssetImage("images/superplayer_ic_vod_pause_normal.png", package: StringResource.PKG_NAME),
)
: Image(
: const Image(
width: 30,
height: 30,
image: AssetImage("images/superplayer_ic_vod_play_normal.png"),
image: AssetImage("images/superplayer_ic_vod_play_normal.png", package: StringResource.PKG_NAME),
),
);
}
......@@ -118,10 +145,10 @@ class _VideoBottomViewState extends State<VideoBottomView> {
visible: _showFullScreenBtn,
child: InkWell(
onTap: onSwitchFullScreen,
child: Image(
child: const Image(
width: 30,
height: 30,
image: AssetImage("images/superplayer_ic_vod_fullscreen.png"),
image: AssetImage("images/superplayer_ic_vod_fullscreen.png", package: StringResource.PKG_NAME),
),
),
);
......@@ -130,29 +157,40 @@ class _VideoBottomViewState extends State<VideoBottomView> {
Widget _getSlider() {
return Expanded(
child: VideoSlider(
key: _sliderView,
min: 0,
max: _videoDuration,
value: _currentDuration,
bufferedValue: _bufferedDuration,
activeColor: Color(ColorResource.COLOR_MAIN_THEME),
inactiveColor: Color(ColorResource.COLOR_GRAY),
sliderColor: Color(ColorResource.COLOR_MAIN_THEME),
activeColor: const Color(ColorResource.COLOR_MAIN_THEME),
inactiveColor: const Color(ColorResource.COLOR_GRAY),
sliderColor: const Color(ColorResource.COLOR_MAIN_THEME),
sliderOutterColor: Colors.white,
progressHeight: 2,
sliderRadius: 4,
sliderOutterRadius: 10,
onPointClick: (double pointX, int pos) {
if (null != keyFrameList) {
setState(() {
showedKeyFrameInfo = keyFrameList![pos];
});
}
},
// 直播禁止时移
canDrag: _playerType == SuperPlayerType.VOD,
playPoints: !_showFullScreenBtn ? _playPoints : [],
onDragUpdate: (value) {
_isOnDraging = true;
_currentDuration = value * _videoDuration;
widget._controller.onSeekChanged(_currentDuration);
},
onDragEnd: (value) {
setState(() {
_isOnDraging = false;
_currentDuration = value * _videoDuration;
widget._playerController.seek(_currentDuration);
LogUtils.d(TAG,
"_currentDuration:$_currentDuration,_videoDuration:$_videoDuration");
LogUtils.d(TAG, "_currentDuration:$_currentDuration,_videoDuration:$_videoDuration");
widget._controller.onSeekEnd();
});
},
),
......@@ -198,7 +236,7 @@ class _VideoBottomViewState extends State<VideoBottomView> {
}
void updatePlayerType(SuperPlayerType type) {
if(_playerType != type) {
if (_playerType != type) {
setState(() {
_playerType = type;
});
......@@ -229,12 +267,28 @@ class _VideoBottomViewState extends State<VideoBottomView> {
});
}
}
void setKeyFrame(List<PlayKeyFrameDescInfo>? keyFrameList) {
this.keyFrameList = keyFrameList;
_playPoints.clear();
if (null != keyFrameList) {
for (PlayKeyFrameDescInfo info in keyFrameList) {
double progress = info.time / _videoDuration;
SliderPoint point = SliderPoint();
point.progress = progress;
_playPoints.add(point);
}
}
setState(() {});
}
}
class _BottomViewController {
class BottomViewController {
Function onTapStart;
Function onTapFullScreen;
Function onTapQuality;
Function(double value) onSeekChanged;
Function onSeekEnd;
_BottomViewController(this.onTapStart, this.onTapFullScreen, this.onTapQuality);
BottomViewController(this.onTapStart, this.onTapFullScreen, this.onTapQuality, this.onSeekChanged, this.onSeekEnd);
}
......@@ -2,10 +2,10 @@
part of demo_super_player_lib;
class SuperPlayerCoverView extends StatefulWidget {
final _CoverViewController _controller;
SuperPlayerModel? videoModel;
final CoverViewController _controller;
final SuperPlayerModel? videoModel;
SuperPlayerCoverView(this._controller, GlobalKey<_SuperPlayerCoverViewState> key, this.videoModel) : super(key: key);
const SuperPlayerCoverView(this._controller, Key key, this.videoModel) : super(key: key);
@override
State<StatefulWidget> createState() => _SuperPlayerCoverViewState();
......@@ -53,7 +53,7 @@ class _SuperPlayerCoverViewState extends State<SuperPlayerCoverView> {
onDoubleTap: _onDoubleTapVideo,
onTap: _onSingleTapVideo,
child: Container(
decoration: BoxDecoration(
decoration: const BoxDecoration(
// 增加一个半透明背景,防止透明封面图的出现
color:Color(ColorResource.COLOR_TRANS_GRAY)
),
......@@ -85,9 +85,9 @@ class _SuperPlayerCoverViewState extends State<SuperPlayerCoverView> {
}
}
class _CoverViewController {
class CoverViewController {
Function onDoubleTapVideo;
Function onSingleTapVideo;
_CoverViewController(this.onDoubleTapVideo, this.onSingleTapVideo);
CoverViewController(this.onDoubleTapVideo, this.onSingleTapVideo);
}
......@@ -5,9 +5,9 @@ typedef BoolFunction = bool Function();
typedef DoubleFunction = double Function();
/// 超级播放器更多菜单
class SuperPlayerMoreView extends StatefulWidget {
_MoreViewController controller;
final MoreViewController controller;
SuperPlayerMoreView(this.controller, {Key? key}) : super(key: key);
const SuperPlayerMoreView(this.controller, {Key? key}) : super(key: key);
@override
State<StatefulWidget> createState() => _SuperPlayerMoreViewState();
......@@ -78,8 +78,8 @@ class _SuperPlayerMoreViewState extends State<SuperPlayerMoreView> {
child: Container(
height: double.infinity,
width: 320,
padding: EdgeInsets.only(left: 15, right: 20, top: 15, bottom: 15),
decoration: BoxDecoration(color: Color(ColorResource.COLOR_TRANS_BLACK)),
padding: const EdgeInsets.only(left: 15, right: 20, top: 15, bottom: 15),
decoration: const BoxDecoration(color: Color(ColorResource.COLOR_TRANS_BLACK)),
child: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
......@@ -96,7 +96,7 @@ class _SuperPlayerMoreViewState extends State<SuperPlayerMoreView> {
Widget _getSwitchHardwareWidget() {
return Container(
margin: EdgeInsets.only(top: 10, bottom: 10),
margin: const EdgeInsets.only(top: 10, bottom: 10),
child: Row(
children: [
Text(
......@@ -105,7 +105,7 @@ class _SuperPlayerMoreViewState extends State<SuperPlayerMoreView> {
style: ThemeResource.getCommonLabelTextStyle(),
),
Switch(
activeColor: Color(ColorResource.COLOR_MAIN_THEME),
activeColor: const Color(ColorResource.COLOR_MAIN_THEME),
value: _isOpenAccelerate,
onChanged: _onChangeAccelerate)
],
......@@ -123,7 +123,7 @@ class _SuperPlayerMoreViewState extends State<SuperPlayerMoreView> {
];
for (String rateStr in playRateStr.keys) {
playRateChild.add(Container(
padding: EdgeInsets.only(left: 5, right: 5),
padding: const EdgeInsets.only(left: 5, right: 5),
child: InkWell(
onTap: () => _onChangePlayRate(rateStr),
child: Text(
......@@ -137,7 +137,7 @@ class _SuperPlayerMoreViewState extends State<SuperPlayerMoreView> {
return Visibility(
visible: _isVodPlay,
child: Container(
margin: EdgeInsets.only(top: 10, bottom: 10),
margin: const EdgeInsets.only(top: 10, bottom: 10),
child: Row(
children: playRateChild,
),
......@@ -147,14 +147,14 @@ class _SuperPlayerMoreViewState extends State<SuperPlayerMoreView> {
Widget _getBrightnessWidget() {
return Container(
margin: EdgeInsets.only(top: 10, bottom: 10),
margin: const EdgeInsets.only(top: 10, bottom: 10),
child: Row(children: [
Text(
StringResource.BRIGHTNESS_LABEL,
textAlign: TextAlign.center,
style: ThemeResource.getCommonLabelTextStyle(),
),
Image(width: 30, height: 30, image: AssetImage("images/superplayer_ic_light_min.png")),
const Image(width: 30, height: 30, image: AssetImage("images/superplayer_ic_light_min.png", package:StringResource.PKG_NAME)),
Expanded(
child: Theme(
data: ThemeResource.getCommonSliderTheme(),
......@@ -165,14 +165,14 @@ class _SuperPlayerMoreViewState extends State<SuperPlayerMoreView> {
onChanged: _onChangeBrightness,
)),
),
Image(width: 30, height: 30, image: AssetImage("images/superplayer_ic_light_max.png")),
const Image(width: 30, height: 30, image: AssetImage("images/superplayer_ic_light_max.png", package:StringResource.PKG_NAME)),
]),
);
}
Widget _getVolumeWidget() {
return Container(
margin: EdgeInsets.only(top: 10, bottom: 10),
margin: const EdgeInsets.only(top: 10, bottom: 10),
child: Row(
children: [
Text(
......@@ -180,7 +180,7 @@ class _SuperPlayerMoreViewState extends State<SuperPlayerMoreView> {
textAlign: TextAlign.center,
style: ThemeResource.getCommonLabelTextStyle(),
),
Image(width: 30, height: 30, image: AssetImage("images/superplayer_ic_volume_min.png")),
const Image(width: 30, height: 30, image: AssetImage("images/superplayer_ic_volume_min.png", package:StringResource.PKG_NAME)),
Expanded(
child: Theme(
data: ThemeResource.getCommonSliderTheme(),
......@@ -191,7 +191,7 @@ class _SuperPlayerMoreViewState extends State<SuperPlayerMoreView> {
onChanged: _onChangeVolume,
)),
),
Image(width: 30, height: 30, image: AssetImage("images/superplayer_ic_volume_max.png")),
const Image(width: 30, height: 30, image: AssetImage("images/superplayer_ic_volume_max.png", package:StringResource.PKG_NAME)),
],
),
);
......@@ -264,12 +264,12 @@ class _SuperPlayerMoreViewState extends State<SuperPlayerMoreView> {
}
}
class _MoreViewController {
class MoreViewController {
BoolFunction getAccelerateIsOpen;
DoubleFunction getPlayRate;
Function(bool value) siwtchAccelerate;
Function(double playRate) onChangedPlayRate;
BoolFunction getIsVodPlay;
_MoreViewController(this.getAccelerateIsOpen, this.getPlayRate, this.siwtchAccelerate, this.onChangedPlayRate, this.getIsVodPlay);
MoreViewController(this.getAccelerateIsOpen, this.getPlayRate, this.siwtchAccelerate, this.onChangedPlayRate, this.getIsVodPlay);
}
......@@ -4,9 +4,9 @@ part of demo_super_player_lib;
class QualityListView extends StatefulWidget {
final List<VideoQuality>? _qualityList;
final VideoQuality? _currentQuality;
final _QualityListViewController _controller;
final QualityListViewController _controller;
QualityListView(this._controller, this._qualityList, this._currentQuality, Key key) : super(key: key);
const QualityListView(this._controller, this._qualityList, this._currentQuality, Key key) : super(key: key);
@override
State<StatefulWidget> createState() => _QualityListViewState();
......@@ -32,8 +32,8 @@ class _QualityListViewState extends State<QualityListView> {
child: Container(
height: double.infinity,
width: 300,
padding: EdgeInsets.only(left: 15, right: 15, top: 15, bottom: 15),
decoration: BoxDecoration(color: Color(ColorResource.COLOR_TRANS_BLACK)),
padding: const EdgeInsets.only(left: 15, right: 15, top: 15, bottom: 15),
decoration: const BoxDecoration(color: Color(ColorResource.COLOR_TRANS_BLACK)),
child: Center(
child: null != _qualityList
? ListView.builder(
......@@ -44,7 +44,7 @@ class _QualityListViewState extends State<QualityListView> {
return InkWell(
onTap: () => widget._controller.onSwitchStream(_qualityList![index]),
child: Container(
margin: EdgeInsets.only(top: 10, bottom: 10),
margin: const EdgeInsets.only(top: 10, bottom: 10),
child: Text(
_qualityList![index].title,
textAlign: TextAlign.center,
......@@ -71,8 +71,8 @@ class _QualityListViewState extends State<QualityListView> {
}
}
class _QualityListViewController {
class QualityListViewController {
Function(VideoQuality) onSwitchStream;
_QualityListViewController(this.onSwitchStream);
QualityListViewController(this.onSwitchStream);
}
......@@ -6,7 +6,7 @@ class _VideoTitleView extends StatefulWidget {
final _VideoTitleController _controller;
final bool initIsFullScreen;
_VideoTitleView(this._controller, this.initIsFullScreen, this._title, GlobalKey<_VideoTitleViewState> key)
const _VideoTitleView(this._controller, this.initIsFullScreen, this._title, GlobalKey<_VideoTitleViewState> key)
: super(key: key);
@override
......@@ -27,32 +27,32 @@ class _VideoTitleViewState extends State<_VideoTitleView> {
@override
Widget build(BuildContext context) {
return Container(
padding: EdgeInsets.only(left: 6, right: 6),
decoration: BoxDecoration(
image: DecorationImage(image: AssetImage("images/superplayer_top_shadow.png"), fit: BoxFit.fill)),
padding: const EdgeInsets.only(left: 6, right: 6),
decoration: const BoxDecoration(
image: DecorationImage(image: AssetImage("images/superplayer_top_shadow.png", package:StringResource.PKG_NAME), fit: BoxFit.fill)),
child: Row(
children: [
InkWell(
onTap: _onTapBackBtn,
child: Image(
child: const Image(
width: 30,
height: 30,
image: AssetImage("images/superplayer_btn_back_play.png"),
image: AssetImage("images/superplayer_btn_back_play.png", package:StringResource.PKG_NAME),
),
),
Text(
_title,
style: TextStyle(fontSize: 11, color: Colors.white),
style: const TextStyle(fontSize: 11, color: Colors.white),
),
Expanded(child: SizedBox()),
const Expanded(child: SizedBox()),
Visibility(
visible: _isFullScreen,
child: InkWell(
onTap: _onTapMore,
child: Image(
child: const Image(
width: 30,
height: 30,
image: AssetImage("images/superplayer_ic_vod_more_normal.png"),
image: AssetImage("images/superplayer_ic_vod_more_normal.png", package:StringResource.PKG_NAME),
),
))
],
......@@ -84,8 +84,8 @@ class _VideoTitleViewState extends State<_VideoTitleView> {
}
class _VideoTitleController {
Function _onTapBack;
Function _onTapMore;
final Function _onTapBack;
final Function _onTapMore;
_VideoTitleController(this._onTapBack, this._onTapMore);
}
......@@ -3,7 +3,7 @@ part of demo_super_player_lib;
/// 视频进度条,双进度
class VideoSlider extends StatefulWidget {
late final _VideoSliderController controller;
late final VideoSliderController controller;
final double? progressHeight;
final double min;
......@@ -17,12 +17,14 @@ class VideoSlider extends StatefulWidget {
final Color? bufferedColor;
final Color? sliderColor;
final Color? sliderOutterColor;
List<SliderPoint>? playPoints = [];
bool? canDrag = true;
// calback
final Function? onDragStart;
final Function(double value)? onDragUpdate;
final Function(double value)? onDragEnd;
final Function(double pointX, int pos)? onPointClick;
VideoSlider(
{this.progressHeight,
......@@ -40,14 +42,18 @@ class VideoSlider extends StatefulWidget {
this.onDragStart,
this.onDragUpdate,
this.onDragEnd,
this.canDrag}) {
this.canDrag,
this.playPoints,
this.onPointClick,
GlobalKey<VideoSliderState>? key})
: super(key: key) {
double range = (max - min);
if (range <= 0) {
controller = _VideoSliderController(1, bufferedProgress: 1);
controller = VideoSliderController(1, bufferedProgress: 1);
} else {
double currentProgress = remainTwoFixed(value / range);
double? bufferedProgress = bufferedValue != null ? remainTwoFixed(bufferedValue! / range) : null;
controller = _VideoSliderController(currentProgress, bufferedProgress: bufferedProgress);
controller = VideoSliderController(currentProgress, bufferedProgress: bufferedProgress);
}
}
......@@ -120,6 +126,19 @@ class VideoSliderState extends State<VideoSlider> {
},
onTapUp: (TapUpDetails details) {
if (widget.canDrag! && !isDraging) {
List<SliderPoint> tmp = widget.playPoints ?? [];
for (int i = 0; i < tmp.length; i++) {
SliderPoint point = tmp[i];
final box = context.findRenderObject()! as RenderBox;
final Offset tapPos = box.globalToLocal(details.globalPosition);
double width = box.size.width;
double pointStart = width * point.progress;
double clickRadius = radius * 3;
if (tapPos.dx > pointStart - clickRadius && tapPos.dx < pointStart + clickRadius) {
widget.onPointClick?.call(pointStart, i);
return;
}
}
_seekToPosition(details.globalPosition);
widget.onDragEnd?.call(widget.controller.progress);
}
......@@ -144,6 +163,7 @@ class VideoSliderState extends State<VideoSlider> {
bufferedPercent: widget.controller.bufferedProgress,
leftPadding: leftPadding,
rightPadding: rightPadding,
pointList: widget.playPoints ?? [],
sliderOverlayRadius: overlayRadius),
),
),
......@@ -151,14 +171,18 @@ class VideoSliderState extends State<VideoSlider> {
);
}
void _seekToPosition(Offset globalPosition) {
double _getProgressByPosition(Offset globalPosition) {
final box = context.findRenderObject()! as RenderBox;
final Offset tapPos = box.globalToLocal(globalPosition);
double progress = widget.controller.progress;
progress = tapPos.dx / box.size.width;
if (progress < 0) progress = 0;
if (progress > 1) progress = 1;
return progress;
}
void _seekToPosition(Offset globalPosition) {
double progress = _getProgressByPosition(globalPosition);
setState(() {
widget.controller.progress = progress;
});
......@@ -175,6 +199,7 @@ class _VideoSliderPainter extends CustomPainter {
final bool isDraging;
final double leftPadding;
final double rightPadding;
final List<SliderPoint> pointList;
_VideoSliderPainter(
{required this.shaders,
......@@ -185,6 +210,7 @@ class _VideoSliderPainter extends CustomPainter {
required this.sliderOverlayRadius,
required this.leftPadding,
required this.rightPadding,
required this.pointList,
this.bufferedPercent});
@override
......@@ -222,6 +248,13 @@ class _VideoSliderPainter extends CustomPainter {
Radius.circular(sliderRadius)),
shaders.progressPaint);
// draw point
for (SliderPoint point in pointList) {
double pointStart = start + (width * point.progress);
shaders.pointPaint.color = point.pointColor;
canvas.drawCircle(Offset(pointStart, size.height / 2), progressHeight, shaders.pointPaint);
}
// draw outer slider,only show when drag
if (isDraging) {
canvas.drawCircle(Offset(progressEndless, size.height / 2), sliderOverlayRadius, shaders.dragSliderOverlayPaint);
......@@ -242,6 +275,7 @@ class _VideoSliderShaders {
Paint progressPaint = Paint();
Paint dragSliderPaint = Paint();
Paint dragSliderOverlayPaint = Paint();
Paint pointPaint = Paint();
_VideoSliderShaders(
{Color? backgroundColor, Color? progressColor, Color? dragSliderColor, Color? bufferedColor, Color? drawSliderOverlayColor}) {
......@@ -253,11 +287,11 @@ class _VideoSliderShaders {
}
}
class _VideoSliderController {
class VideoSliderController {
double progress;
double? bufferedProgress;
_VideoSliderController(this.progress, {this.bufferedProgress});
VideoSliderController(this.progress, {this.bufferedProgress});
}
void _checkRange(double value, {String? valueName}) {
......
// Copyright (c) 2022 Tencent. All rights reserved.
part of demo_super_player_lib;
final double topBottomOffset = 0;
const topBottomOffset = 0.0;
int manualOrientationDirection = TXVodPlayEvent.ORIENTATION_LANDSCAPE_RIGHT;
FullScreenController _fullScreenController = FullScreenController();
......@@ -9,14 +9,14 @@ FullScreenController _fullScreenController = FullScreenController();
class SuperPlayerView extends StatefulWidget {
final SuperPlayerController _controller;
SuperPlayerView(this._controller, {Key? viewKey}) : super(key: viewKey);
const SuperPlayerView(this._controller, {Key? viewKey}) : super(key: viewKey);
@override
State<StatefulWidget> createState() => SuperPlayerViewState();
}
class SuperPlayerViewState extends State<SuperPlayerView> with WidgetsBindingObserver {
static final int _controlViewShowTime = 7000;
static const _controlViewShowTime = 7000;
static const TAG = "SuperPlayerViewState";
late SuperPlayerController _playController;
......@@ -32,28 +32,32 @@ class SuperPlayerViewState extends State<SuperPlayerView> with WidgetsBindingObs
double _videoHeight = 0;
bool _isShowControlView = false;
bool _isShowQualityView = false;
bool _isShowQualityListView = false;
late _BottomViewController _bottomViewController;
late _QualityListViewController _qualitListViewController;
late BottomViewController _bottomViewController;
late QualityListViewController _qualitListViewController;
late _VideoTitleController _titleViewController;
late _SuperPlayerFullScreenController _superPlayerFullUIController;
late _CoverViewController _coverViewController;
late _MoreViewController _moreViewController;
late CoverViewController _coverViewController;
late MoreViewController _moreViewController;
StreamSubscription? _volumeSubscription;
StreamSubscription? _pipSubscription;
/// init
Timer _controlViewTimer = Timer(Duration(milliseconds: _controlViewShowTime), () {});
Timer _controlViewTimer = Timer(const Duration(milliseconds: _controlViewShowTime), () {});
GlobalKey<_VideoBottomViewState> _videoBottomKey = GlobalKey();
GlobalKey<_QualityListViewState> _qualityListKey = GlobalKey();
GlobalKey<_VideoTitleViewState> _videoTitleKey = GlobalKey();
GlobalKey<_SuperPlayerCoverViewState> _coverViewKey = GlobalKey();
GlobalKey<_SuperPlayerMoreViewState> _moreViewKey = GlobalKey();
GlobalKey<_SuperPlayerFloatState> floatPlayerKey = GlobalKey();
final GlobalKey<_VideoBottomViewState> _videoBottomKey = GlobalKey();
final GlobalKey<_QualityListViewState> _qualityListKey = GlobalKey();
final GlobalKey<_VideoTitleViewState> _videoTitleKey = GlobalKey();
final GlobalKey<_SuperPlayerCoverViewState> _coverViewKey = GlobalKey();
final GlobalKey<_SuperPlayerMoreViewState> _moreViewKey = GlobalKey();
final GlobalKey<_SuperPlayerFloatState> floatPlayerKey = GlobalKey();
Uint8List? _currentSprite;
bool _isShowSprite = false;
/// 任务队列
final TaskExecutors _taskExecutors = TaskExecutors();
@override
void initState() {
......@@ -63,12 +67,16 @@ class SuperPlayerViewState extends State<SuperPlayerView> with WidgetsBindingObs
_titleViewController = _VideoTitleController(_onTapBack, () {
_moreViewKey.currentState?.toggleShowMoreView();
});
_bottomViewController = _BottomViewController(_onTapPlayControl, _onControlFullScreen, _onControlQualityListView);
_coverViewController = _CoverViewController(_onDoubleTapVideo, _onSingleTapVideo);
_qualitListViewController = _QualityListViewController((quality) {
_bottomViewController = BottomViewController(_onTapPlayControl, _onControlFullScreen, _onControlQualityListView, (value) {
_taskExecutors.addTask(() => _controlTest(true, value));
}, () {
_taskExecutors.addTask(() => _controlTest(false, 0));
});
_coverViewController = CoverViewController(_onDoubleTapVideo, _onSingleTapVideo);
_qualitListViewController = QualityListViewController((quality) {
_playController.switchStream(quality);
});
_moreViewController = _MoreViewController(
_moreViewController = MoreViewController(
() => _playController._isOpenHWAcceleration,
() => _playController.currentPlayRate,
(value) => _playController.enableHardwareDecode(value),
......@@ -212,6 +220,7 @@ class SuperPlayerViewState extends State<SuperPlayerView> with WidgetsBindingObs
_qualityListKey.currentState?.updateQuality(qualityList, defaultQuality);
}, (info, list) {
// onVideoImageSpriteAndKeyFrameChanged
_videoBottomKey.currentState?.setKeyFrame(list);
}, () {
// onSysBackPress
_onControlFullScreen();
......@@ -282,8 +291,7 @@ class SuperPlayerViewState extends State<SuperPlayerView> with WidgetsBindingObs
// 不更新状态,直接resume
_playController.getCurrentController().resume();
// 从后台回来之后,如果手机横竖屏状态发生更改,被改为竖屏,那么这里根据判断切换横屏
if (_playController._playerUIStatus == SuperPlayerUIStatus.FULLSCREEN_MODE &&
defaultTargetPlatform == TargetPlatform.iOS) {
if (_playController._playerUIStatus == SuperPlayerUIStatus.FULLSCREEN_MODE && defaultTargetPlatform == TargetPlatform.iOS) {
Orientation currentOrientation = MediaQuery.of(context).orientation;
bool isLandscape = currentOrientation == Orientation.landscape;
if (!isLandscape) {
......@@ -354,9 +362,9 @@ class SuperPlayerViewState extends State<SuperPlayerView> with WidgetsBindingObs
_getPlayer(),
_getTitleArea(),
_getPipEnterView(),
_getImageSpriteView(),
_getCover(),
_getBottomView(),
_getQualityView(),
_getStartOrResumeBtn(),
_getQualityListView(),
_getMoreMenuView(),
......@@ -366,10 +374,18 @@ class SuperPlayerViewState extends State<SuperPlayerView> with WidgetsBindingObs
);
}
Widget _getImageSpriteView() {
return Visibility(
visible: _isShowSprite,
child: Center(
child: null != _currentSprite ? Image.memory(_currentSprite!) : Container(),
),
);
}
Widget _getPipEnterView() {
return Visibility(
visible: _isShowControlView &&
_playController._playerUIStatus == SuperPlayerUIStatus.WINDOW_MODE,
visible: _isShowControlView && _playController._playerUIStatus == SuperPlayerUIStatus.WINDOW_MODE,
child: Positioned(
right: 10,
top: 0,
......@@ -382,7 +398,7 @@ class SuperPlayerViewState extends State<SuperPlayerView> with WidgetsBindingObs
child: Image(
width: 30,
height: 30,
image: AssetImage("images/ic_pip_play_icon.png"),
image: AssetImage("images/ic_pip_play_icon.png", package: StringResource.PKG_NAME),
),
)),
),
......@@ -394,20 +410,10 @@ class SuperPlayerViewState extends State<SuperPlayerView> with WidgetsBindingObs
return SuperPlayerMoreView(_moreViewController, key: _moreViewKey);
}
Widget _getQualityView() {
return Visibility(
visible: _isShowQualityView,
child: ListView.builder(itemBuilder: (BuildContext context, int index) {
return Container();
}),
);
}
Widget _getQualityListView() {
return Visibility(
visible: _isShowQualityListView,
child: QualityListView(_qualitListViewController, _playController.currentQualiyList,
_playController.currentQuality, _qualityListKey),
child: QualityListView(_qualitListViewController, _playController.currentQualiyList, _playController.currentQuality, _qualityListKey),
);
}
......@@ -442,7 +448,7 @@ class SuperPlayerViewState extends State<SuperPlayerView> with WidgetsBindingObs
child: Image(
width: 40,
height: 40,
image: AssetImage("images/superplayer_ic_vod_play_normal.png"),
image: AssetImage("images/superplayer_ic_vod_play_normal.png", package: StringResource.PKG_NAME),
),
),
),
......@@ -462,8 +468,7 @@ class SuperPlayerViewState extends State<SuperPlayerView> with WidgetsBindingObs
child: Center(
child: AspectRatio(
aspectRatio: _aspectRatio,
child: TXPlayerVideo(
controller: _playController.getCurrentController(), playerStream: _playController.getPlayerStream())),
child: TXPlayerVideo(controller: _playController.getCurrentController(), playerStream: _playController.getPlayerStream())),
),
);
}
......@@ -475,11 +480,8 @@ class SuperPlayerViewState extends State<SuperPlayerView> with WidgetsBindingObs
top: topBottomOffset,
left: 0,
right: 0,
child: _VideoTitleView(
_titleViewController,
_playController._playerUIStatus == SuperPlayerUIStatus.FULLSCREEN_MODE,
_playController._getPlayName(),
_videoTitleKey),
child: _VideoTitleView(_titleViewController, _playController._playerUIStatus == SuperPlayerUIStatus.FULLSCREEN_MODE,
_playController._getPlayName(), _videoTitleKey),
),
);
}
......@@ -487,10 +489,10 @@ class SuperPlayerViewState extends State<SuperPlayerView> with WidgetsBindingObs
void _onEnterPipMode() async {
if (!_isFloatingMode) {
int result = await _playController.enterPictureInPictureMode(
backIcon: "images/ic_pip_play_replay.png",
playIcon: "images/ic_pip_play_normal.png",
pauseIcon: "images/ic_pip_play_pause.png",
forwardIcon: "images/ic_pip_play_forward.png");
backIcon: "packages/${StringResource.PKG_NAME}/images/ic_pip_play_replay.png",
playIcon: "packages/${StringResource.PKG_NAME}/images/ic_pip_play_normal.png",
pauseIcon: "packages/${StringResource.PKG_NAME}/images/ic_pip_play_pause.png",
forwardIcon: "packages/${StringResource.PKG_NAME}/images/ic_pip_play_forward.png");
String failedStr = "";
if (result != TXVodPlayEvent.NO_ERROR) {
if (result == TXVodPlayEvent.ERROR_PIP_LOWER_VERSION) {
......@@ -565,6 +567,8 @@ class SuperPlayerViewState extends State<SuperPlayerView> with WidgetsBindingObs
} else if (playerState == SuperPlayerState.PAUSE) {
//继续播放
_playController.resume();
_isShowCover = false;
_coverViewKey.currentState?.hideCover();
}
}
......@@ -617,17 +621,25 @@ class SuperPlayerViewState extends State<SuperPlayerView> with WidgetsBindingObs
}
if (isNeedAutoDisappear) {
_controlViewTimer = new Timer(Duration(milliseconds: _controlViewShowTime), () {
_startHideRunnable();
}
}
void _startHideRunnable() {
if (_controlViewTimer.isActive) {
_controlViewTimer.cancel();
}
_controlViewTimer = Timer(const Duration(milliseconds: _controlViewShowTime), () {
hideControlView();
});
}
}
/// 隐藏所有控制组件
void hideControlView() {
if (!_isShowControlView || !mounted) {
return;
}
/// 隐藏moreView
_moreViewKey.currentState?.hideShowMoreView();
setState(() {
......@@ -636,6 +648,26 @@ class SuperPlayerViewState extends State<SuperPlayerView> with WidgetsBindingObs
});
}
/// 控制雪碧图显示
Future _controlTest(bool isShow, double value) async {
if (isShow) {
Uint8List? tmp = await _playController._vodPlayerController.getImageSprite(value);
if (!Utils.compareBuffer(_currentSprite, tmp)) {
setState(() {
_currentSprite = tmp;
_isShowSprite = true;
});
}
_controlViewTimer.cancel();
} else {
_startHideRunnable();
setState(() {
_currentSprite = null;
_isShowSprite = false;
});
}
}
@override
void dispose() {
WidgetsBinding.instance.removeObserver(this);
......@@ -664,6 +696,7 @@ class SuperPlayerFullScreenState extends State<SuperPlayerFullScreenView> {
@override
Widget build(BuildContext context) {
return WillPopScope(
onWillPop: _onFullScreenWillPop,
child: MediaQuery.removePadding(
context: context,
removeTop: true,
......@@ -672,12 +705,10 @@ class SuperPlayerFullScreenState extends State<SuperPlayerFullScreenView> {
removeRight: true,
child: Scaffold(
body: Container(
decoration: BoxDecoration(color: Colors.black),
decoration: const BoxDecoration(color: Colors.black),
width: double.infinity,
child: SuperPlayerView(widget._playController, viewKey: widget.key)),
)),
onWillPop: _onFullScreenWillPop,
);
)));
}
Future<bool> _onFullScreenWillPop() async {
......@@ -714,7 +745,6 @@ class _SuperPlayerFloatState extends State<SuperPlayerFloatView> {
double _aspectRatio = 16.0 / 9.0;
double _videoWidth = 0;
double _videoHeight = 0;
StreamSubscription? sizeStreamSubscription;
@override
void initState() {
......@@ -797,8 +827,6 @@ class _SuperPlayerFloatState extends State<SuperPlayerFloatView> {
@override
void dispose() {
super.dispose();
// 移除的时候,解除对进度事件的订阅
sizeStreamSubscription?.cancel();
}
}
......
name: superplayer_widget
description: superplayer,base on vodplayer
version: "1.0.1"
environment:
sdk: '>=2.12.0 <3.0.0'
flutter: ">=2.0.0"
dependencies:
flutter:
sdk: flutter
auto_orientation: ^2.2.1
flutter_easyloading: ^3.0.0
super_player:
# When depending on this package from a real application you should use:
# super_player: ^x.y.z
# See https://dart.dev/tools/pub/dependencies#version-constraints
# The example app is bundled with the plugin so we use a path dependency on
# the parent directory to use the current plugin's version.
path: ../
dev_dependencies:
flutter_test:
sdk: flutter
flutter_lints: ^2.0.0
# For information on the generic Dart part of this file, see the
# following page: https://dart.dev/tools/pub/pubspec
# The following section is specific to Flutter packages.
flutter:
uses-material-design: true
assets:
- images/ic_pip_play_icon.png
- images/ic_pip_play_normal.png
- images/ic_pip_play_pause.png
- images/ic_pip_play_forward.png
- images/ic_pip_play_replay.png
- images/superplayer_ic_vod_more_normal.png
- images/superplayer_ic_light_max.png
- images/superplayer_ic_light_min.png
- images/superplayer_ic_volume_max.png
- images/superplayer_ic_volume_min.png
- images/superplayer_ic_vod_pause_normal.png
- images/superplayer_ic_vod_play_normal.png
- images/superplayer_bottom_shadow.png
- images/superplayer_ic_vod_fullscreen.png
- images/superplayer_top_shadow.png
- images/superplayer_btn_back_play.png
# To add assets to your package, add an assets section, like this:
# assets:
# - images/a_dot_burr.jpeg
# - images/a_dot_ham.jpeg
#
# For details regarding assets in packages, see
# https://flutter.dev/assets-and-images/#from-packages
#
# An image asset can refer to one or more resolution-specific "variants", see
# https://flutter.dev/assets-and-images/#resolution-aware
# To add custom fonts to your package, add a fonts section here,
# in this "flutter" section. Each entry in this list should have a
# "family" key with the font family name, and a "fonts" key with a
# list giving the asset and other descriptors for the font. For
# example:
# fonts:
# - family: Schyler
# fonts:
# - asset: fonts/Schyler-Regular.ttf
# - asset: fonts/Schyler-Italic.ttf
# style: italic
# - family: Trajan Pro
# fonts:
# - asset: fonts/TrajanPro.ttf
# - asset: fonts/TrajanPro_Bold.ttf
# weight: 700
#
# For details regarding fonts in packages, see
# https://flutter.dev/custom-fonts/#from-packages
Markdown 格式
0%
您添加了 0 到此讨论。请谨慎行事。
请先完成此评论的编辑!
注册 或者 后发表评论