import 'package:better_player_plus/better_player_plus.dart'; import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:news_app/constant/size_res.dart'; import 'package:news_app/ui/video/video_play_item_widget.dart'; import 'package:news_app/util/log.util.dart'; import 'package:visibility_detector/visibility_detector.dart'; import 'package:fluttertoast/fluttertoast.dart'; import '../../model/video_new_model.dart'; import '../../provider/video_commend_provider.dart'; /// @author: bo.zeng /// @email: cnhbwds@gmail.com /// @date: 2025 2025/4/9 16:00 /// @description: class VideoRecommendListPage extends ConsumerStatefulWidget { const VideoRecommendListPage({super.key}); @override ConsumerState createState() => _VideoRecommendListPageState(); } final recommendListProvider = NotifierProvider>(() { return VideoRecommendProvider(); }); // 全局共享的视频控制器 BetterPlayerController? globalVideoController; class _VideoRecommendListPageState extends ConsumerState with AutomaticKeepAliveClientMixin { late final PageController _pageController; int _currentPageIndex = 0; String? _currentVideoUrl; String? _lastFailedVideoUrl; // 记录上次失败的视频URL int _consecutiveFailures = 0; // 连续失败计数 @override void initState() { super.initState(); _pageController = PageController(); // 先获取视频数据 ref.read(recommendListProvider.notifier).fetchRecommendVideos(); } // 安全地执行控制器操作 void _safeControllerOperation(VoidCallback operation) { if (!mounted) return; final controller = globalVideoController; if (controller == null) return; try { if (controller.isVideoInitialized() == true) { operation(); } } catch (e) { consoleLog("Controller operation error: $e"); // 控制器可能已被释放,重置为 null globalVideoController = null; } } void _playVideo(String url) { consoleLog('===== _playVideo called with URL: $url ====='); if (!mounted) return; // 检查现有控制器是否可用(未释放且是同一视频) if (_currentVideoUrl == url && globalVideoController != null) { try { if (globalVideoController!.isVideoInitialized() == true) { _safeControllerOperation(() { globalVideoController!.play(); }); return; } } catch (e) { consoleLog("Controller check failed, creating new one: $e"); } } _currentVideoUrl = url; // 释放旧控制器 try { globalVideoController?.dispose(); } catch (e) { consoleLog("Dispose old controller error: $e"); } globalVideoController = null; consoleLog("Creating BetterPlayerDataSource with url: $url"); // 创建数据源(与 video_detail_page 一致) // 对于m4v格式,使用other格式让ExoPlayer自动检测 final dataSource = BetterPlayerDataSource( BetterPlayerDataSourceType.network, url, videoFormat: BetterPlayerVideoFormat.other, // 让ExoPlayer自动检测格式 notificationConfiguration: BetterPlayerNotificationConfiguration( showNotification: false, ), ); // 创建新控制器,宽度占满、高度自适应 globalVideoController = BetterPlayerController( BetterPlayerConfiguration( autoPlay: true, looping: true, fit: BoxFit.fitWidth, // 宽度占满,高度自适应 controlsConfiguration: const BetterPlayerControlsConfiguration( showControls: false, ), handleLifecycle: false, autoDetectFullscreenAspectRatio: false, // 尝试使用软件解码作为后备 errorBuilder: (context, errorMessage) { consoleLog("GlobalVideoController errorBuilder: $errorMessage"); return Center( child: Text( "视频加载失败", style: TextStyle(color: Colors.white, fontSize: 14), ), ); }, ), betterPlayerDataSource: dataSource, ); consoleLog("BetterPlayerController created"); // 先添加事件监听 globalVideoController!.addEventsListener((event) { consoleLog("GlobalVideoController event: ${event.betterPlayerEventType}"); if (event.betterPlayerEventType == BetterPlayerEventType.exception) { // 打印详细异常信息 consoleLog("Video exception occurred"); consoleLog("Exception parameters: ${event.parameters}"); consoleLog("Exception all data: ${event.toString()}"); // 检查是否是硬件解码器不支持的错误 final exception = event.parameters?['exception']?.toString() ?? ''; if (exception.contains('NO_EXCEEDS_CAPABILITIES') || exception.contains('MediaCodecVideoRenderer error')) { consoleLog("Hardware decoder not supported for this video"); // 显示提示并跳过该视频 if (mounted && _consecutiveFailures < 3) { Fluttertoast.showToast( msg: "该视频格式暂不支持,自动跳过", toastLength: Toast.LENGTH_SHORT, gravity: ToastGravity.CENTER, ); // 延迟后跳到下一个视频 Future.delayed(const Duration(milliseconds: 800), () { if (mounted && _currentPageIndex < ref.read(recommendListProvider).length - 1) { _consecutiveFailures++; _pageController.animateToPage( _currentPageIndex + 1, duration: const Duration(milliseconds: 300), curve: Curves.easeInOut, ); } else { // 已经是最后一个视频了,重置计数 _consecutiveFailures = 0; } }); } } } else if (event.betterPlayerEventType == BetterPlayerEventType.play) { consoleLog("Video started playing"); _consecutiveFailures = 0; // 播放成功,重置失败计数 } else if (event.betterPlayerEventType == BetterPlayerEventType.bufferingStart) { consoleLog("Buffering started"); } else if (event.betterPlayerEventType == BetterPlayerEventType.bufferingEnd) { consoleLog("Buffering ended"); } }); // 尝试播放 Future.delayed(const Duration(milliseconds: 50), () { if (!mounted || globalVideoController == null) return; _safeControllerOperation(() { globalVideoController!.play(); }); }); } @override void dispose() { _pageController.dispose(); // 释放全局控制器 try { globalVideoController?.dispose(); } catch (e) { consoleLog("Dispose globalVideoController error: $e"); } globalVideoController = null; super.dispose(); } bool _isVisible = false; void _onVisibilityChanged(VisibilityInfo info) { if (!mounted) return; final visible = info.visibleFraction > 0.5; if (_isVisible != visible) { setState(() { _isVisible = visible; }); } if (visible) { consoleLog("页面可见"); // 页面重新可见时,恢复播放(仅在控制器已初始化时) try { if (globalVideoController != null && globalVideoController!.isVideoInitialized() == true) { globalVideoController!.play(); } } catch (e) { consoleLog("Visibility play error: $e"); } } else { consoleLog("页面不可见"); // 暂停播放 try { if (globalVideoController != null && globalVideoController!.isVideoInitialized() == true) { globalVideoController!.pause(); } } catch (e) { consoleLog("Visibility pause error: $e"); } } } void _onPageChanged(int index) { if (!mounted) return; setState(() { _currentPageIndex = index; }); // 页面切换后,播放新视频 final videos = ref.read(recommendListProvider); if (videos.isNotEmpty && index >= 0 && index < videos.length) { final url = videos[index].url; if (url != null && url.isNotEmpty) { _playVideo(url); } } } @override Widget build(BuildContext context) { super.build(context); final videos = ref.watch(recommendListProvider); // 初始化控制器并播放第一个视频 if (videos.isNotEmpty && globalVideoController == null) { WidgetsBinding.instance.addPostFrameCallback((_) { final firstUrl = videos.firstOrNull?.url; if (firstUrl != null && firstUrl.isNotEmpty && mounted) { _playVideo(firstUrl); } }); } // 视频列表为空时显示加载状态 if (videos.isEmpty) { return const Center( child: CircularProgressIndicator(), ); } return VisibilityDetector( key: const Key("value"), onVisibilityChanged: _onVisibilityChanged, child: Padding( padding: EdgeInsets.symmetric( horizontal: horizontalPadding, vertical: horizontalPadding, ), child: PageView.builder( controller: _pageController, onPageChanged: _onPageChanged, scrollDirection: Axis.vertical, itemCount: videos.length, itemBuilder: (context, index) { // 添加边界检查 if (index < 0 || index >= videos.length) { return const SizedBox.shrink(); } final item = videos[index]; return VideoPlayItemWidget( key: ValueKey("video_${item.contentId ?? index}"), item: item, isActive: _isVisible && _currentPageIndex == index, index: index, ); }, ), ), ); } @override bool get wantKeepAlive => true; }