432 lines
14 KiB
Dart
432 lines
14 KiB
Dart
import 'package:easy_debounce/easy_throttle.dart';
|
|
import 'dart:io';
|
|
|
|
import 'package:flutter/cupertino.dart';
|
|
import 'package:flutter/material.dart';
|
|
import 'package:flutter_kinetra/kt_pages/kt_mine/logic.dart';
|
|
import 'package:flutter_kinetra/kt_pages/kt_short_video/state.dart';
|
|
import 'package:flutter_kinetra/kt_utils/kt_string_extend.dart';
|
|
import 'package:flutter_kinetra/kt_utils/kt_toast_utils.dart';
|
|
import 'package:flutter_screenutil/flutter_screenutil.dart';
|
|
import 'package:get/get.dart';
|
|
import 'package:video_player/video_player.dart';
|
|
|
|
import '../../dio_cilent/kt_apis.dart';
|
|
import '../../dio_cilent/kt_request.dart';
|
|
import '../../kt_model/kt_video_detail_bean.dart';
|
|
import '../../kt_utils/kt_device_info_utils.dart';
|
|
import '../../kt_widgets/kt_status_widget.dart';
|
|
import '../../kt_widgets/kt_store_widget.dart';
|
|
import '../kt_mine/kt_store/logic.dart';
|
|
|
|
class VideoPlayLogic extends GetxController {
|
|
final state = VideoPlayState();
|
|
|
|
late final PageController pageController;
|
|
List<VideoPlayerController?> controllers = [];
|
|
KtLoadStatusType videoStatus = KtLoadStatusType.loading;
|
|
KtLoadStatusType loadStatusType = KtLoadStatusType.loadSuccess;
|
|
int currentIndex = 0;
|
|
bool _disposed = false;
|
|
final userLogic = Get.put(KtMineLogic());
|
|
|
|
@override
|
|
void onInit() {
|
|
super.onInit();
|
|
pageController = PageController(initialPage: currentIndex);
|
|
}
|
|
|
|
@override
|
|
void onReady() {
|
|
super.onReady();
|
|
_disposed = false;
|
|
state.shortPlayId = Get.arguments['shortPlayId'];
|
|
state.videoId = Get.arguments['videoId'] ?? 0;
|
|
state.imageUrl = Get.arguments['imageUrl'] ?? '';
|
|
state.activityId = Get.arguments['activityId'];
|
|
state.isFromDiscover = Get.arguments['isFromDiscover'] ?? false;
|
|
fetchData();
|
|
}
|
|
|
|
@override
|
|
void onClose() {
|
|
_disposed = true;
|
|
clearCacheCtrl();
|
|
super.onClose();
|
|
}
|
|
|
|
clearCacheCtrl() {
|
|
for (var controller in controllers) {
|
|
controller?.pause();
|
|
controller?.dispose();
|
|
}
|
|
controllers.clear();
|
|
}
|
|
|
|
Future<void> fetchData({bool toPage = true}) async {
|
|
Map<String, dynamic> params = {
|
|
'short_play_id': state.shortPlayId,
|
|
"video_id": state.videoId,
|
|
};
|
|
if (state.activityId != null) {
|
|
params['activity_id'] = state.activityId;
|
|
}
|
|
clearCacheCtrl();
|
|
try {
|
|
ApiResponse res = await KtHttpClient().request(
|
|
KtApis.getVideoDetails,
|
|
method: HttpMethod.get,
|
|
queryParameters: params,
|
|
);
|
|
if (res.success) {
|
|
state.loadStatus = KtLoadStatusType.loadSuccess;
|
|
state.video = VideoDetailBean.fromJson(res.data);
|
|
if (state.imageUrl == '') {
|
|
state.imageUrl = state.video!.shortPlayInfo?.imageUrl ?? '';
|
|
}
|
|
if (state.videoId == 0) {
|
|
state.videoId = state.video?.videoInfo?.shortPlayVideoId ?? 0;
|
|
}
|
|
state.episodeList = state.video?.episodeList ?? [];
|
|
if (toPage) currentIndex = (state.video?.videoInfo?.episode ?? 1) - 1;
|
|
for (var video in state.episodeList) {
|
|
if (video.isLock == true) {
|
|
state.curUnlock = state.episodeList.indexOf(video);
|
|
break;
|
|
}
|
|
}
|
|
controllers = List<VideoPlayerController?>.filled(
|
|
state.episodeList.length,
|
|
null,
|
|
growable: true,
|
|
);
|
|
if (toPage) {
|
|
onPageChanged(
|
|
currentIndex,
|
|
isToggle: true,
|
|
isUploadHistorySeconds: false,
|
|
);
|
|
} else {
|
|
_initializeController(currentIndex);
|
|
}
|
|
|
|
update();
|
|
} else {
|
|
state.loadStatus = KtLoadStatusType.loadFailed;
|
|
update();
|
|
}
|
|
if (userLogic.state.userInfo.isVip == true) state.curUnlock = 9999;
|
|
update();
|
|
} catch (e) {
|
|
state.loadStatus = KtLoadStatusType.loadFailed;
|
|
update();
|
|
}
|
|
}
|
|
|
|
void reportHistory() {
|
|
if (currentIndex < 0 || currentIndex >= state.episodeList.length) return;
|
|
Map<String, dynamic> params = {
|
|
"short_play_id": state.shortPlayId,
|
|
"video_id": state.episodeList[currentIndex].id,
|
|
};
|
|
KtHttpClient().request(KtApis.createHistory, data: params);
|
|
}
|
|
|
|
void reportActivity() {
|
|
Map<String, dynamic> params = {
|
|
'short_play_id': state.shortPlayId,
|
|
"short_play_video_id": state.episodeList[currentIndex].shortPlayVideoId,
|
|
"activity_id": state.activityId,
|
|
};
|
|
KtHttpClient().request(KtApis.activeAfterWatchingVideo, data: params);
|
|
}
|
|
|
|
// 切换剧集时处理视频状态
|
|
Future<void> onPageChanged(
|
|
int index, {
|
|
bool isToggle = false,
|
|
bool isUploadHistorySeconds = true,
|
|
int type = 0,
|
|
}) async {
|
|
// EasyThrottle.throttle('page-change', Duration(seconds: 3), () async {
|
|
debugPrint('--$type-to-index:$index');
|
|
if (index < 0 || index >= state.episodeList.length) return;
|
|
|
|
// 暂停当前视频
|
|
if (controllers[currentIndex]?.value.isPlaying ?? false) {
|
|
await controllers[currentIndex]?.pause();
|
|
if (isUploadHistorySeconds) {
|
|
uploadHistorySeconds(
|
|
controllers[currentIndex]?.value.position.inMilliseconds ?? 0,
|
|
);
|
|
}
|
|
}
|
|
if (controllers[currentIndex]?.value.isCompleted ?? false) {
|
|
if (isUploadHistorySeconds) uploadHistorySeconds(0);
|
|
if (state.activityId != null) reportActivity();
|
|
}
|
|
if (isToggle) {
|
|
loadStatusType = KtLoadStatusType.loading;
|
|
update();
|
|
|
|
await _initializeController(index);
|
|
loadStatusType = KtLoadStatusType.loadSuccess;
|
|
update();
|
|
WidgetsBinding.instance.addPostFrameCallback((_) {
|
|
pageController.jumpToPage(index);
|
|
});
|
|
}
|
|
currentIndex = index;
|
|
if (state.episodeList[index].isLock == true) {
|
|
controllers[index]?.seekTo(Duration(seconds: 0));
|
|
controllers[index]?.pause();
|
|
update();
|
|
// EasyThrottle.throttle(
|
|
// 'show-buy-dialog',
|
|
// Duration(milliseconds: 3000),
|
|
// () => showStoreDialog(state.episodeList[index].coins ?? 0),
|
|
// );
|
|
return;
|
|
}
|
|
|
|
if (controllers[index] != null) {
|
|
if (state.curUnlock > index || userLogic.state.userInfo.isVip == true) {
|
|
controllers[index]?.play();
|
|
}
|
|
} else {
|
|
if (!isToggle) await _initializeController(index);
|
|
if (state.curUnlock > index || userLogic.state.userInfo.isVip == true) {
|
|
controllers[index]?.play();
|
|
}
|
|
}
|
|
controllers[index]?.setPlaybackSpeed(state.currentSpeed);
|
|
// updateHomeVideo();
|
|
// print('----curIndex:$currentIndex');
|
|
// 预加载新的相邻视频,并释放多余控制器
|
|
_preloadAdjacentVideos();
|
|
update();
|
|
reportHistory();
|
|
// });
|
|
}
|
|
|
|
// 释放非当前、前后的视频控制器,减少内存占用
|
|
void _releaseUnusedControllers() {
|
|
for (int i = 0; i < controllers.length; i++) {
|
|
if (i < currentIndex - 1 || i > currentIndex + 1) {
|
|
controllers[i]?.dispose();
|
|
controllers[i] = null;
|
|
}
|
|
}
|
|
}
|
|
|
|
// 初始化视频控制器
|
|
Future<void> _initializeController(int index) async {
|
|
if (index < 0 || index >= state.episodeList.length) return;
|
|
if (controllers[index] != null) return;
|
|
|
|
final episode = state.episodeList[index];
|
|
VideoPlayerController controller =
|
|
Platform.isAndroid && KtDeviceInfoUtil().osVersion == '10'
|
|
? VideoPlayerController.networkUrl(
|
|
Uri.parse(episode.videoUrl!),
|
|
formatHint: VideoFormat.hls,
|
|
viewType: VideoViewType.platformView,
|
|
)
|
|
: VideoPlayerController.networkUrl(
|
|
Uri.parse(episode.videoUrl!),
|
|
formatHint: VideoFormat.hls,
|
|
);
|
|
|
|
controllers[index] = controller;
|
|
|
|
try {
|
|
await controller.initialize();
|
|
if (index == currentIndex && (episode.isLock == false || userLogic.state.userInfo.isVip == true)) {
|
|
controller.play();
|
|
update();
|
|
}
|
|
controller.setPlaybackSpeed(state.currentSpeed);
|
|
debugPrint('---seekTo:${episode.playSeconds}');
|
|
controller.addListener(() {
|
|
if (currentIndex == index && !_disposed) update();
|
|
if (currentIndex == state.episodeList.length - 1 &&
|
|
(controllers.last?.value.isCompleted ?? false)) {
|
|
// showRecommendDialog();
|
|
}
|
|
if (controller.value.isCompleted && !controller.value.isBuffering) {
|
|
onPageChanged(index + 1, isToggle: true);
|
|
}
|
|
});
|
|
} catch (e) {
|
|
// 可根据需要处理异常
|
|
// UserUtil().reportErrorEvent(
|
|
// 'video initialize failed',
|
|
// UserUtil.videoError,
|
|
// errMsg: e.toString(),
|
|
// payData: episode.toJson(),
|
|
// shortPlayId: episode.shortPlayId ?? 0,
|
|
// shortPlayVideoId: episode.shortPlayVideoId ?? 0,
|
|
// );
|
|
debugPrint('---err:$e');
|
|
}
|
|
}
|
|
|
|
// 预加载相邻视频
|
|
void _preloadAdjacentVideos() {
|
|
if (currentIndex > 0) _initializeController(currentIndex - 1);
|
|
if (currentIndex < state.episodeList.length - 1) {
|
|
_initializeController(currentIndex + 1);
|
|
}
|
|
_releaseUnusedControllers();
|
|
}
|
|
|
|
void uploadHistorySeconds(int playSeconds) {
|
|
Map<String, dynamic> params = {
|
|
"short_play_id": state.shortPlayId,
|
|
"video_id": state.episodeList[currentIndex].id,
|
|
"play_seconds": playSeconds > 0 ? playSeconds : 0,
|
|
};
|
|
KtHttpClient().request(KtApis.uploadHistorySeconds, data: params);
|
|
}
|
|
|
|
Future<void> likeVideo() async {
|
|
if (state.video == null) return;
|
|
|
|
Map<String, dynamic> params = {
|
|
"short_play_id": state.video?.shortPlayInfo?.shortPlayId,
|
|
"video_id": state.episodeList[currentIndex].id,
|
|
};
|
|
if (state.video?.shortPlayInfo?.isCollect ?? false) {
|
|
await KtHttpClient().request(KtApis.deleteFavoriteVideo, data: params);
|
|
} else {
|
|
await KtHttpClient().request(KtApis.collectVideo, data: params);
|
|
}
|
|
|
|
state.video?.shortPlayInfo?.isCollect =
|
|
!(state.video?.shortPlayInfo?.isCollect ?? false);
|
|
update();
|
|
}
|
|
|
|
// 购买剧集
|
|
buyVideo(num videoId, num coins, {bool toRecharge = false}) async {
|
|
ApiResponse res = await KtHttpClient().request(
|
|
KtApis.buyVideo,
|
|
data: {"short_play_id": state.shortPlayId, "video_id": videoId},
|
|
);
|
|
if (res.success) {
|
|
String status = res.data['status'];
|
|
if (status == 'success') {
|
|
int index = state.episodeList.indexWhere((item) => item.id == videoId);
|
|
state.episodeList[index].isLock = false;
|
|
if (index != state.episodeList.length - 1) {
|
|
state.curUnlock = index + 1;
|
|
}
|
|
update();
|
|
onPageChanged(index, isToggle: true);
|
|
final mineLogic = Get.put(KtMineLogic());
|
|
mineLogic.getUserInfo();
|
|
} else if (res.data['status'] == 'not_enough') {
|
|
KtToastUtils.showError('Coin not enough');
|
|
if (toRecharge) showStoreDialog(coins);
|
|
// if (toRecharge) state.detailBean?.payMode == 1 ? showBuyCoinDialog(coins) : showVipBuyDialog(coins);
|
|
} else if (status == 'jump') {
|
|
KtToastUtils.showError('Cannot jump episode');
|
|
}
|
|
} else {
|
|
KtToastUtils.showError('Purchase failed');
|
|
}
|
|
}
|
|
|
|
showStoreDialog(num coins) {
|
|
Get.put(KtStoreLogic());
|
|
// EasyThrottle.throttle('restore-short', Duration(seconds: 3), () => BuyUtils.restorePay(showTips: false));
|
|
Get.bottomSheet(
|
|
isScrollControlled: true,
|
|
Container(
|
|
width: ScreenUtil().screenWidth,
|
|
height: ScreenUtil().screenHeight - kToolbarHeight,
|
|
padding: EdgeInsets.fromLTRB(15.w, 72.w, 15.w, 10.w),
|
|
decoration: BoxDecoration(
|
|
image: DecorationImage(image: AssetImage('recharge_bg.png'.ktIcon)),
|
|
),
|
|
child: SingleChildScrollView(
|
|
child: Column(
|
|
children: [
|
|
Row(
|
|
mainAxisAlignment: MainAxisAlignment.center,
|
|
|
|
children: [
|
|
Text(
|
|
'Balance',
|
|
style: TextStyle(fontSize: 12.sp, color: Colors.black),
|
|
),
|
|
Image.asset('ic_coin.png'.ktIcon, width: 20.w),
|
|
GetBuilder<KtMineLogic>(
|
|
builder: (logic) {
|
|
return Text(
|
|
'${(logic.state.userInfo.coinLeftTotal ?? 0) + (logic.state.userInfo.sendCoinLeftTotal ?? 0)}',
|
|
style: TextStyle(fontSize: 13.sp, color: Colors.black),
|
|
);
|
|
},
|
|
),
|
|
SizedBox(width: 2.w),
|
|
Image.asset('ic_coin.png'.ktIcon, width: 16.w),
|
|
Container(
|
|
width: 1.w,
|
|
height: 10.w,
|
|
margin: EdgeInsets.symmetric(horizontal: 18.w),
|
|
color: Color(0xFF5E5E5E),
|
|
),
|
|
Text(
|
|
'Unlock:',
|
|
style: TextStyle(fontSize: 12.sp, color: Colors.black),
|
|
),
|
|
Image.asset('ic_coin.png'.ktIcon, width: 20.w),
|
|
SizedBox(width: 2.w),
|
|
Text(
|
|
'$coins',
|
|
style: TextStyle(
|
|
fontSize: 13.sp,
|
|
color: Colors.black,
|
|
fontWeight: FontWeight.w500,
|
|
),
|
|
),
|
|
],
|
|
),
|
|
GetBuilder<KtStoreLogic>(
|
|
builder: (logic) {
|
|
return KtStoreWidget(
|
|
store: logic.state.storeBean,
|
|
isStoreDialog: true,
|
|
onItemTap: (goods) async {
|
|
logic.func = () {
|
|
Get.back();
|
|
fetchData(toPage: false);
|
|
buyVideo(
|
|
state.episodeList[currentIndex].id!,
|
|
coins,
|
|
toRecharge: false,
|
|
);
|
|
// checkVipCoinSub();
|
|
};
|
|
EasyThrottle.throttle(
|
|
'buy',
|
|
Duration(seconds: 3),
|
|
() async => await logic.buyGoods(
|
|
goods,
|
|
shortPlayId: state.shortPlayId,
|
|
videoId: state.episodeList[currentIndex].id,
|
|
),
|
|
);
|
|
},
|
|
);
|
|
},
|
|
),
|
|
],
|
|
),
|
|
),
|
|
),
|
|
);
|
|
}
|
|
}
|