|
@@ -5,6 +5,7 @@ import 'package:news_app/constant/size_res.dart';
|
|
|
import 'package:news_app/ui/video/video_play_item_widget.dart';
|
|
import 'package:news_app/ui/video/video_play_item_widget.dart';
|
|
|
import 'package:news_app/util/log.util.dart';
|
|
import 'package:news_app/util/log.util.dart';
|
|
|
import 'package:visibility_detector/visibility_detector.dart';
|
|
import 'package:visibility_detector/visibility_detector.dart';
|
|
|
|
|
+import 'package:fluttertoast/fluttertoast.dart';
|
|
|
|
|
|
|
|
import '../../model/video_new_model.dart';
|
|
import '../../model/video_new_model.dart';
|
|
|
import '../../provider/video_commend_provider.dart';
|
|
import '../../provider/video_commend_provider.dart';
|
|
@@ -34,6 +35,8 @@ class _VideoRecommendListPageState extends ConsumerState<VideoRecommendListPage>
|
|
|
late final PageController _pageController;
|
|
late final PageController _pageController;
|
|
|
int _currentPageIndex = 0;
|
|
int _currentPageIndex = 0;
|
|
|
String? _currentVideoUrl;
|
|
String? _currentVideoUrl;
|
|
|
|
|
+ String? _lastFailedVideoUrl; // 记录上次失败的视频URL
|
|
|
|
|
+ int _consecutiveFailures = 0; // 连续失败计数
|
|
|
|
|
|
|
|
@override
|
|
@override
|
|
|
void initState() {
|
|
void initState() {
|
|
@@ -61,82 +64,141 @@ class _VideoRecommendListPageState extends ConsumerState<VideoRecommendListPage>
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
void _playVideo(String url) {
|
|
void _playVideo(String url) {
|
|
|
|
|
+ consoleLog('===== _playVideo called with URL: $url =====');
|
|
|
if (!mounted) return;
|
|
if (!mounted) return;
|
|
|
|
|
|
|
|
- // 如果是同一个视频,不需要重新加载
|
|
|
|
|
|
|
+ // 检查现有控制器是否可用(未释放且是同一视频)
|
|
|
if (_currentVideoUrl == url && globalVideoController != null) {
|
|
if (_currentVideoUrl == url && globalVideoController != null) {
|
|
|
- _safeControllerOperation(() {
|
|
|
|
|
- globalVideoController!.play();
|
|
|
|
|
- });
|
|
|
|
|
- return;
|
|
|
|
|
|
|
+ try {
|
|
|
|
|
+ if (globalVideoController!.isVideoInitialized() == true) {
|
|
|
|
|
+ _safeControllerOperation(() {
|
|
|
|
|
+ globalVideoController!.play();
|
|
|
|
|
+ });
|
|
|
|
|
+ return;
|
|
|
|
|
+ }
|
|
|
|
|
+ } catch (e) {
|
|
|
|
|
+ consoleLog("Controller check failed, creating new one: $e");
|
|
|
|
|
+ }
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
_currentVideoUrl = url;
|
|
_currentVideoUrl = url;
|
|
|
|
|
|
|
|
- // 创建新控制器
|
|
|
|
|
- globalVideoController = null; // 释放旧的
|
|
|
|
|
|
|
+ // 释放旧控制器
|
|
|
|
|
+ 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(
|
|
globalVideoController = BetterPlayerController(
|
|
|
BetterPlayerConfiguration(
|
|
BetterPlayerConfiguration(
|
|
|
autoPlay: true,
|
|
autoPlay: true,
|
|
|
looping: true,
|
|
looping: true,
|
|
|
- fit: BoxFit.fitWidth, // 宽度固定,高度自适应
|
|
|
|
|
|
|
+ fit: BoxFit.fitWidth, // 宽度占满,高度自适应
|
|
|
controlsConfiguration: const BetterPlayerControlsConfiguration(
|
|
controlsConfiguration: const BetterPlayerControlsConfiguration(
|
|
|
showControls: false,
|
|
showControls: false,
|
|
|
),
|
|
),
|
|
|
handleLifecycle: false,
|
|
handleLifecycle: false,
|
|
|
autoDetectFullscreenAspectRatio: false,
|
|
autoDetectFullscreenAspectRatio: false,
|
|
|
|
|
+ // 尝试使用软件解码作为后备
|
|
|
errorBuilder: (context, errorMessage) {
|
|
errorBuilder: (context, errorMessage) {
|
|
|
- consoleLog("GlobalVideoController error: $errorMessage");
|
|
|
|
|
- // 返回空 widget,不显示错误
|
|
|
|
|
- return const SizedBox.shrink();
|
|
|
|
|
|
|
+ consoleLog("GlobalVideoController errorBuilder: $errorMessage");
|
|
|
|
|
+ return Center(
|
|
|
|
|
+ child: Text(
|
|
|
|
|
+ "视频加载失败",
|
|
|
|
|
+ style: TextStyle(color: Colors.white, fontSize: 14),
|
|
|
|
|
+ ),
|
|
|
|
|
+ );
|
|
|
},
|
|
},
|
|
|
),
|
|
),
|
|
|
|
|
+ betterPlayerDataSource: dataSource,
|
|
|
);
|
|
);
|
|
|
|
|
|
|
|
- // 先添加事件监听,然后再设置数据源
|
|
|
|
|
|
|
+ consoleLog("BetterPlayerController created");
|
|
|
|
|
+
|
|
|
|
|
+ // 先添加事件监听
|
|
|
globalVideoController!.addEventsListener((event) {
|
|
globalVideoController!.addEventsListener((event) {
|
|
|
consoleLog("GlobalVideoController event: ${event.betterPlayerEventType}");
|
|
consoleLog("GlobalVideoController event: ${event.betterPlayerEventType}");
|
|
|
if (event.betterPlayerEventType == BetterPlayerEventType.exception) {
|
|
if (event.betterPlayerEventType == BetterPlayerEventType.exception) {
|
|
|
|
|
+ // 打印详细异常信息
|
|
|
consoleLog("Video exception occurred");
|
|
consoleLog("Video exception occurred");
|
|
|
- // 静默处理异常
|
|
|
|
|
- }
|
|
|
|
|
- });
|
|
|
|
|
|
|
+ consoleLog("Exception parameters: ${event.parameters}");
|
|
|
|
|
+ consoleLog("Exception all data: ${event.toString()}");
|
|
|
|
|
|
|
|
- Future.delayed(const Duration(milliseconds: 50), () {
|
|
|
|
|
- if (!mounted || globalVideoController == null) return;
|
|
|
|
|
|
|
+ // 检查是否是硬件解码器不支持的错误
|
|
|
|
|
+ final exception = event.parameters?['exception']?.toString() ?? '';
|
|
|
|
|
+ if (exception.contains('NO_EXCEEDS_CAPABILITIES') ||
|
|
|
|
|
+ exception.contains('MediaCodecVideoRenderer error')) {
|
|
|
|
|
+ consoleLog("Hardware decoder not supported for this video");
|
|
|
|
|
|
|
|
- try {
|
|
|
|
|
- final dataSource = BetterPlayerDataSource(
|
|
|
|
|
- BetterPlayerDataSourceType.network,
|
|
|
|
|
- url,
|
|
|
|
|
- videoFormat: BetterPlayerVideoFormat.other,
|
|
|
|
|
- notificationConfiguration: BetterPlayerNotificationConfiguration(
|
|
|
|
|
- showNotification: false,
|
|
|
|
|
- ),
|
|
|
|
|
- );
|
|
|
|
|
-
|
|
|
|
|
- globalVideoController!.setupDataSource(dataSource).then((_) {
|
|
|
|
|
- if (mounted && globalVideoController != null) {
|
|
|
|
|
- _safeControllerOperation(() {
|
|
|
|
|
- globalVideoController!.play();
|
|
|
|
|
|
|
+ // 显示提示并跳过该视频
|
|
|
|
|
+ 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;
|
|
|
|
|
+ }
|
|
|
});
|
|
});
|
|
|
}
|
|
}
|
|
|
- }).catchError((error) {
|
|
|
|
|
- consoleLog("Video setup error: $error");
|
|
|
|
|
- // 静默处理错误,不显示错误提示
|
|
|
|
|
- });
|
|
|
|
|
- } catch (e) {
|
|
|
|
|
- consoleLog("Video play error: $e");
|
|
|
|
|
- // 静默处理错误,不显示错误提示
|
|
|
|
|
|
|
+ }
|
|
|
|
|
+ } 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
|
|
@override
|
|
|
void dispose() {
|
|
void dispose() {
|
|
|
_pageController.dispose();
|
|
_pageController.dispose();
|
|
|
- // 暂停播放但不dispose,因为可能被其他页面使用
|
|
|
|
|
- globalVideoController?.pause();
|
|
|
|
|
|
|
+ // 释放全局控制器
|
|
|
|
|
+ try {
|
|
|
|
|
+ globalVideoController?.dispose();
|
|
|
|
|
+ } catch (e) {
|
|
|
|
|
+ consoleLog("Dispose globalVideoController error: $e");
|
|
|
|
|
+ }
|
|
|
|
|
+ globalVideoController = null;
|
|
|
super.dispose();
|
|
super.dispose();
|
|
|
}
|
|
}
|
|
|
|
|
|
|
@@ -152,19 +214,26 @@ class _VideoRecommendListPageState extends ConsumerState<VideoRecommendListPage>
|
|
|
}
|
|
}
|
|
|
if (visible) {
|
|
if (visible) {
|
|
|
consoleLog("页面可见");
|
|
consoleLog("页面可见");
|
|
|
- // 页面重新可见时,重新初始化视频以确保状态正确
|
|
|
|
|
- final videos = ref.read(recommendListProvider);
|
|
|
|
|
- if (videos.isNotEmpty && _currentPageIndex < videos.length) {
|
|
|
|
|
- final url = videos[_currentPageIndex].url;
|
|
|
|
|
- if (url != null && url.isNotEmpty) {
|
|
|
|
|
- _playVideo(url);
|
|
|
|
|
|
|
+ // 页面重新可见时,恢复播放(仅在控制器已初始化时)
|
|
|
|
|
+ try {
|
|
|
|
|
+ if (globalVideoController != null &&
|
|
|
|
|
+ globalVideoController!.isVideoInitialized() == true) {
|
|
|
|
|
+ globalVideoController!.play();
|
|
|
}
|
|
}
|
|
|
|
|
+ } catch (e) {
|
|
|
|
|
+ consoleLog("Visibility play error: $e");
|
|
|
}
|
|
}
|
|
|
} else {
|
|
} else {
|
|
|
consoleLog("页面不可见");
|
|
consoleLog("页面不可见");
|
|
|
- _safeControllerOperation(() {
|
|
|
|
|
- globalVideoController?.pause();
|
|
|
|
|
- });
|
|
|
|
|
|
|
+ // 暂停播放
|
|
|
|
|
+ try {
|
|
|
|
|
+ if (globalVideoController != null &&
|
|
|
|
|
+ globalVideoController!.isVideoInitialized() == true) {
|
|
|
|
|
+ globalVideoController!.pause();
|
|
|
|
|
+ }
|
|
|
|
|
+ } catch (e) {
|
|
|
|
|
+ consoleLog("Visibility pause error: $e");
|
|
|
|
|
+ }
|
|
|
}
|
|
}
|
|
|
}
|
|
}
|
|
|
|
|
|