import 'package:easy_debounce/easy_throttle.dart'; import 'dart:io'; import 'dart:async'; import 'package:flutter/cupertino.dart'; import 'package:flutter/material.dart'; import 'package:card_swiper/card_swiper.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_kinetra/kt_pages/kt_home/logic.dart'; import 'package:flutter_kinetra/kt_pages/kt_my_list/logic.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_model/kt_short_video_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_widgets/kt_dialog.dart'; import '../kt_mine/kt_store/logic.dart'; import '../../kt_widgets/kt_network_image.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()); final mylistLogic = Get.put(MyListLogic()); final mylistState = Get.find().state; @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; startTimer(); fetchData(); getRecommend(); startVideoTimer(); } @override void onClose() { _disposed = true; clearCacheCtrl(); state.timer?.cancel(); state.videotimer?.cancel(); super.onClose(); } // 推荐点击 initData(shortPlayId, imageUrl) { uploadHistorySeconds( controllers[currentIndex]?.value.position.inMilliseconds ?? 0, ); Get.back(); state.shortPlayId = shortPlayId; state.videoId = 0; state.imageUrl = imageUrl; state.isRecommend = false; // pageController.dispose(); currentIndex = 0; pageController.jumpToPage(currentIndex); // pageController = PageController(initialPage: currentIndex); clearCacheCtrl(); fetchData(); state.recommendList.clear(); getRecommend(); startTimer(); startVideoTimer(); } 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(); } } // 5s之后返回推荐 void startTimer() { state.timer = Timer(const Duration(seconds: 5), () { state.isRecommend = true; update(); }); } // 5s 之后隐藏控制器 void startVideoTimer() { state.videotimer?.cancel(); state.videotimer = Timer(const Duration(seconds: 5), () { state.isVideoctrHide = true; update(); }); } // 获取推荐列表 getRecommend() async { ApiResponse res = await KtHttpClient().request( KtApis.getDetailsRecommand, method: HttpMethod.get, ); if (res.success) { state.recommendList = [ ...res.data['list'].map((item) => KtShortVideoBean.fromJson(item)), ]; 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); } void updateHomeVideo() { final homeLogic = Get.put(KtHomeLogic()); final homeState = Get.find().state; int playTime = controllers[currentIndex]?.value.position.inSeconds ?? 0; homeState.curVideo = KtShortVideoBean() ..shortPlayId = state.shortPlayId ..imageUrl = state.video?.shortPlayInfo?.imageUrl ..name = state.video?.shortPlayInfo?.name ..playTime = playTime ..process = currentIndex + 1; homeLogic.update(); } // 切换剧集时处理视频状态 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); cancelCollect(state.video!.shortPlayInfo!.shortPlayId!); } else { await KtHttpClient().request(KtApis.collectVideo, data: params); state.video?.shortPlayInfo?.isCollect = !(state.video?.shortPlayInfo?.isCollect ?? false); update(); mylistLogic.getCollectList(refresh: true); mylistLogic.getHistoryList(refresh: true); } } // 购买剧集 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, ), ); }, ); }, ), ], ), ), ), ); } cancelCollect(num id) async { Get.dialog( KtDialog( title: 'Remove from your list?', subTitle: 'This drama will be removed from your saved list.', rightBtnText: 'Remove', rightBtnIcon: 'ic_dialog_delete.png', rightBtnFunc: () async { ApiResponse res = await KtHttpClient().request( KtApis.deleteFavoriteVideo, queryParameters: {'short_play_id': id}, ); if (res.success) { state.video?.shortPlayInfo?.isCollect = !(state.video?.shortPlayInfo?.isCollect ?? false); update(); // state.favoriteList.removeWhere((item) => id == item.shortPlayId); mylistState.chestList.removeWhere((item) => id == item.shortPlayId); mylistState.historyList .firstWhereOrNull((item) => id == item.shortPlayId) ?.isCollect = 0; mylistLogic.update(); } }, ), ); } showRecommendDialog() { EasyThrottle.throttle('show-recommend', Duration(seconds: 3), () async { controllers[currentIndex]?.pause(); Get.bottomSheet( isScrollControlled: true, isDismissible: false, enableDrag: false, Stack( children: [ Positioned( top: ScreenUtil().statusBarHeight + 10.w, left: 16.w, child: GestureDetector( onTap: () { EasyThrottle.throttle( 'back-recommend', Duration(seconds: 3), () async { Get.back(); Get.back(); }, ); }, child: Image.asset('ic_back_white.png'.ktIcon, width: 24.w), ), ), Positioned( bottom: 0, left: 0, child: Container( height: 503.w, width: ScreenUtil().screenWidth, padding: EdgeInsets.fromLTRB(0.w, 75.w, 0.w, 20.w), decoration: BoxDecoration( image: DecorationImage( image: AssetImage('video_recommend_bg.png'.ktIcon), fit: BoxFit.fill, ), ), child: Column( children: [ Row( children: [ SizedBox(width: 15.w), Image.asset('ip.png'.ktIcon, width: 38.w, height: 40.w), SizedBox(width: 6.w), Container( height: 30.w, padding: EdgeInsets.symmetric(horizontal: 14.w), decoration: BoxDecoration( color: Color(0xFF1E1E20), borderRadius: BorderRadius.circular(15.w), ), child: Center( child: Text( 'More Drama Gold Below!', style: TextStyle( color: Color(0xFFA7F62F), fontSize: 16.sp, fontWeight: FontWeight.w500, ), ), ), ), ], ), SizedBox( height: 290.w, child: Swiper( layout: SwiperLayout.CUSTOM, customLayoutOption: CustomLayoutOption(startIndex: -1, stateCount: 3) ..addRotate([-20.0 / 180, 0.0, 20.0 / 180]) ..addTranslate([ Offset(-220.w, 0), Offset(0, 0), Offset(220.w, 0), ]), itemWidth: 190.w, itemHeight: 226.w, itemBuilder: (context, index) { final item = state.recommendList[index % state.recommendList.length]; return GestureDetector( onTap: () { initData( state.recommendList[index].shortPlayId ?? -1, state.recommendList[index].imageUrl ?? '', ); }, child: KtNetworkImage( imageUrl: item.imageUrl ?? '', width: 190.w, height: 226.w, borderRadius: BorderRadius.circular(20.w), ), ); }, itemCount: state.recommendList.length, loop: true, autoplay: true, onIndexChanged: (index) => state.recommendIndex = index, ), ), Row( mainAxisAlignment: MainAxisAlignment.center, children: [ GestureDetector( onTap: () { initData( state .recommendList[state.recommendIndex] .shortPlayId ?? -1, state .recommendList[state.recommendIndex] .imageUrl ?? '', ); }, child: Container( width: 280.w, height: 64.w, padding: EdgeInsets.only(bottom: 16.w), decoration: BoxDecoration( image: DecorationImage( image: AssetImage( 'video_recommend_btn.png'.ktIcon, ), fit: BoxFit.cover, ), ), child: Container( height: 48.w, child: Row( mainAxisAlignment: MainAxisAlignment.center, children: [ Image.asset( 'video_recommend_play.png'.ktIcon, width: 18.w, height: 18.w, ), SizedBox(width: 4.w), Text( 'watch now', style: TextStyle( color: Colors.black, fontSize: 14.sp, fontWeight: FontWeight.w500, ), ), ], ), ), ), ), ], ), ], ), ), ), ], ), ); }); } }