2025-09-23 15:09:18 +08:00

422 lines
14 KiB
Dart

import 'package:easy_debounce/easy_throttle.dart';
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_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];
final controller = 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,
),
);
},
);
},
),
],
),
),
),
);
}
}