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 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 fetchData({bool toPage = true}) async { Map 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.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 params = { "short_play_id": state.shortPlayId, "video_id": state.episodeList[currentIndex].id, }; KtHttpClient().request(KtApis.createHistory, data: params); } void reportActivity() { Map 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 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 _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 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 likeVideo() async { if (state.video == null) return; Map 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( 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( 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, ), ); }, ); }, ), ], ), ), ), ); } }