盐城新淘科技有限公司 hace 1 semana
padre
commit
5402f03b57

+ 36 - 7
xinhuaribao/lib/provider/video_commend_provider.dart

@@ -14,16 +14,45 @@ import '../ui/me/user_favorite_page.dart';
 class VideoRecommendProvider extends Notifier<List<VideoNewModel>> {
   @override
   List<VideoNewModel> build() {
-    return List<VideoNewModel>.empty();
+    return const <VideoNewModel>[];
   }
 
   Future<void> fetchRecommendVideos() async {
-    final jsonData = await HttpUtil().get(apiVideoRecommend);
-    final data = ModelParser.parseList<VideoNewModel>(
-      jsonData,
-      VideoNewModel.fromJson,
-    );
-    state = data;
+    try {
+      final jsonData = await HttpUtil().get(apiVideoRecommend);
+
+      // 确保 jsonData 是一个 List
+      if (jsonData == null) {
+        state = const <VideoNewModel>[];
+        return;
+      }
+
+      // 如果 jsonData 是 Map 且有 data 字段,提取 data
+      final List<dynamic> dataList;
+      if (jsonData is Map) {
+        final data = jsonData['data'];
+        if (data is List) {
+          dataList = data;
+        } else {
+          state = const <VideoNewModel>[];
+          return;
+        }
+      } else if (jsonData is List) {
+        dataList = jsonData;
+      } else {
+        state = const <VideoNewModel>[];
+        return;
+      }
+
+      final data = ModelParser.parseList<VideoNewModel>(
+        dataList,
+        VideoNewModel.fromJson,
+      );
+      state = data;
+    } catch (e) {
+      // 出错时保持空列表状态
+      state = const <VideoNewModel>[];
+    }
   }
 
   Future<void> fetchVideoLike({

+ 1 - 6
xinhuaribao/lib/ui/video/video_detail_page.dart

@@ -191,17 +191,12 @@ class _VideoDetailPageState extends ConsumerState<VideoDetailPage>
 
   @override
   void dispose() {
+    _betterPlayerController?.pause();
     _betterPlayerController?.dispose();
     subscription?.close();
     super.dispose();
   }
 
-  @override
-  void deactivate() {
-    super.deactivate();
-    _betterPlayerController?.pause();
-  }
-
   @override
   void initState() {
     super.initState();

+ 47 - 76
xinhuaribao/lib/ui/video/video_play_item_widget.dart

@@ -6,7 +6,6 @@ import 'package:flutter_riverpod/flutter_riverpod.dart';
 import 'package:flutter_screenutil/flutter_screenutil.dart';
 import 'package:go_router/go_router.dart';
 import 'package:news_app/ui/video/video_detail_page.dart';
-import 'package:news_app/ui/video/video_recommend_list_page.dart';
 
 import '../../constant/config.dart';
 import '../../gen/assets.gen.dart';
@@ -16,23 +15,26 @@ import '../../util/share_util.dart';
 import '../../widget/auth_gesture_detector.dart';
 import '../../widget/load_image.dart';
 import '../../widget/my_txt.dart';
+import '../video/video_recommend_list_page.dart';
 
 class VideoPlayItemWidget extends ConsumerStatefulWidget {
   final VideoNewModel item;
   final bool isActive;
+  final int index;
 
   const VideoPlayItemWidget({
     super.key,
     required this.item,
     required this.isActive,
+    required this.index,
   });
 
   @override
   ConsumerState<VideoPlayItemWidget> createState() => _VideoItemWidgetState();
 }
 
-class _VideoItemWidgetState extends ConsumerState<VideoPlayItemWidget> {
-  late BetterPlayerController _controller;
+class _VideoItemWidgetState extends ConsumerState<VideoPlayItemWidget>
+    with AutomaticKeepAliveClientMixin {
   bool _isPlaying = false;
 
   bool _isWeChatInstalled = false;
@@ -40,14 +42,19 @@ class _VideoItemWidgetState extends ConsumerState<VideoPlayItemWidget> {
   Future<void> _checkWeChatInstallation() async {
     if (Platform.isAndroid) {
       final installed = await isWeChatInstalledOnlyAndroid();
-      setState(() => _isWeChatInstalled = installed);
+      if (mounted) {
+        setState(() => _isWeChatInstalled = installed);
+      }
     } else if (Platform.isIOS) {
       final installed = await fluwx.isWeChatInstalled;
-      setState(() => _isWeChatInstalled = installed);
+      if (mounted) {
+        setState(() => _isWeChatInstalled = installed);
+      }
     }
   }
 
   Future<void> shareAction(VideoNewModel data) async {
+    if (!mounted) return;
     showModalBottomSheet(
       context: context,
       builder:
@@ -57,16 +64,9 @@ class _VideoItemWidgetState extends ConsumerState<VideoPlayItemWidget> {
               mainAxisAlignment: MainAxisAlignment.spaceBetween,
               children: [
                 SizedBox(height: 10.h),
-                Container(
+                SizedBox(
                   width: double.infinity,
                   height: 80.h,
-                  decoration: BoxDecoration(
-                    borderRadius: BorderRadius.only(
-                      topLeft: Radius.circular(10.r),
-                      topRight: Radius.circular(10.r),
-                    ),
-                    color: Colors.white,
-                  ),
                   child: Row(
                     crossAxisAlignment: CrossAxisAlignment.center,
                     mainAxisAlignment: MainAxisAlignment.spaceBetween,
@@ -80,7 +80,7 @@ class _VideoItemWidgetState extends ConsumerState<VideoPlayItemWidget> {
                               url: data.shareUrl ?? "",
                             );
                           },
-                          child: Container(
+                          child: SizedBox(
                             width: 100.w,
                             height: 80.h,
                             child: Column(
@@ -112,7 +112,7 @@ class _VideoItemWidgetState extends ConsumerState<VideoPlayItemWidget> {
                               url: data.shareUrl ?? "",
                             );
                           },
-                          child: Container(
+                          child: SizedBox(
                             width: 100.w,
                             height: 80.h,
                             child: Column(
@@ -142,7 +142,7 @@ class _VideoItemWidgetState extends ConsumerState<VideoPlayItemWidget> {
                             url: data.shareUrl ?? "",
                           );
                         },
-                        child: Container(
+                        child: SizedBox(
                           width: 100.w,
                           height: 80.h,
                           child: Column(
@@ -177,71 +177,34 @@ class _VideoItemWidgetState extends ConsumerState<VideoPlayItemWidget> {
   void initState() {
     super.initState();
     _checkWeChatInstallation();
-    _controller = BetterPlayerController(
-      BetterPlayerConfiguration(
-        autoPlay: false,
-        looping: true,
-        aspectRatio: 16 / 9,
-        fit: BoxFit.fitWidth,
-        controlsConfiguration: const BetterPlayerControlsConfiguration(
-          showControls: false,
-        ),
-      ),
-      betterPlayerDataSource: BetterPlayerDataSource(
-        BetterPlayerDataSourceType.network,
-        widget.item.url ?? "",
-      ),
-    );
-    _controller.addEventsListener(_handlePlayerEvent);
-    // 如果初始化时就是激活状态,立即播放
-    if (widget.isActive && mounted) {
-      _controller.play();
-    }
-  }
-
-  void _handlePlayerEvent(BetterPlayerEvent event) {
-    if (!mounted) return;
-    if (event.betterPlayerEventType == BetterPlayerEventType.play ||
-        event.betterPlayerEventType == BetterPlayerEventType.pause ||
-        event.betterPlayerEventType == BetterPlayerEventType.finished) {
-      final isPlaying = _controller.isPlaying() ?? false;
-      if (mounted && isPlaying != _isPlaying) {
-        setState(() {
-          _isPlaying = isPlaying;
-        });
-      }
-    }
-  }
-
-  @override
-  void didUpdateWidget(covariant VideoPlayItemWidget oldWidget) {
-    super.didUpdateWidget(oldWidget);
-    if (!mounted) return;
-    if (widget.isActive && !oldWidget.isActive) {
-      _controller.play();
-    } else if (!widget.isActive && oldWidget.isActive) {
-      _controller.pause();
-    }
-  }
-
-  @override
-  void dispose() {
-    _controller.removeEventsListener(_handlePlayerEvent);
-    _controller.dispose();
-    super.dispose();
   }
 
   void _togglePlay() {
     if (!mounted) return;
+    final controller = globalVideoController;
+    if (controller == null) return;
+
+    // 分别处理每个操作,确保错误被捕获
     if (_isPlaying) {
-      _controller.pause();
+      try {
+        controller.pause();
+      } catch (e) {
+        // 控制器可能已被释放,忽略错误
+      }
     } else {
-      _controller.play();
+      try {
+        controller.play();
+      } catch (e) {
+        // 控制器可能已被释放,忽略错误
+      }
     }
   }
 
   @override
   Widget build(BuildContext context) {
+    super.build(context);
+    final controller = globalVideoController;
+
     return GestureDetector(
       onTap: _togglePlay,
       child: Stack(
@@ -249,9 +212,14 @@ class _VideoItemWidgetState extends ConsumerState<VideoPlayItemWidget> {
         children: [
           ColoredBox(
             color: Colors.black,
-            child: BetterPlayer(controller: _controller),
+            child: controller != null && widget.isActive
+                ? AspectRatio(
+                    aspectRatio: 16 / 9,
+                    child: BetterPlayer(controller: controller),
+                  )
+                : const SizedBox.shrink(),
           ),
-          if (!_isPlaying)
+          if (!_isPlaying && widget.isActive && controller != null)
             Center(
               child: Image.asset(
                 Assets.images.playIcon.path,
@@ -267,6 +235,7 @@ class _VideoItemWidgetState extends ConsumerState<VideoPlayItemWidget> {
               children: [
                 AuthGestureDetector(
                   onTap: () {
+                    if (!mounted) return;
                     ref
                         .read(recommendListProvider.notifier)
                         .fetchVideoLike(
@@ -292,6 +261,7 @@ class _VideoItemWidgetState extends ConsumerState<VideoPlayItemWidget> {
                 SizedBox(height: 15.h),
                 AuthGestureDetector(
                   onTap: () {
+                    if (!mounted) return;
                     context.push(
                       "/video/detail",
                       extra: VideoParam(
@@ -313,6 +283,7 @@ class _VideoItemWidgetState extends ConsumerState<VideoPlayItemWidget> {
                 SizedBox(height: 15.h),
                 AuthGestureDetector(
                   onTap: () {
+                    if (!mounted) return;
                     ref
                         .read(recommendListProvider.notifier)
                         .fetchVideoFavorite(
@@ -340,10 +311,7 @@ class _VideoItemWidgetState extends ConsumerState<VideoPlayItemWidget> {
                 SizedBox(height: 15.h),
                 GestureDetector(
                   onTap: () {
-                    // shareUrl(
-                    //   title: widget.item.shareDesc ?? "",
-                    //   url: widget.item.shareUrl ?? "",
-                    // );
+                    if (!mounted) return;
                     shareAction(widget.item);
                     if (uuid.isNotEmpty) {
                       ref
@@ -372,4 +340,7 @@ class _VideoItemWidgetState extends ConsumerState<VideoPlayItemWidget> {
       ),
     );
   }
+
+  @override
+  bool get wantKeepAlive => true;
 }

+ 140 - 10
xinhuaribao/lib/ui/video/video_recommend_list_page.dart

@@ -1,3 +1,4 @@
+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';
@@ -23,31 +24,133 @@ class VideoRecommendListPage extends ConsumerStatefulWidget {
 final recommendListProvider =
     NotifierProvider<VideoRecommendProvider, List<VideoNewModel>>(() {
       return VideoRecommendProvider();
-    });
+});
+
+// 全局共享的视频控制器
+BetterPlayerController? globalVideoController;
 
 class _VideoRecommendListPageState extends ConsumerState<VideoRecommendListPage>
     with AutomaticKeepAliveClientMixin {
+  late final PageController _pageController;
+  int _currentPageIndex = 0;
+  String? _currentVideoUrl;
+
   @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 {
+      operation();
+    } catch (e) {
+      // 控制器可能已被释放,重置为 null
+      globalVideoController = null;
+    }
+  }
+
+  void _playVideo(String url) {
+    if (!mounted) return;
+
+    // 如果是同一个视频,不需要重新加载
+    if (_currentVideoUrl == url && globalVideoController != null) {
+      _safeControllerOperation(() {
+        globalVideoController!.play();
+      });
+      return;
+    }
+
+    _currentVideoUrl = url;
+
+    // 创建新控制器
+    globalVideoController = null; // 释放旧的
+    globalVideoController = BetterPlayerController(
+      BetterPlayerConfiguration(
+        autoPlay: true,
+        looping: true,
+        aspectRatio: 16 / 9,
+        fit: BoxFit.fitWidth,
+        controlsConfiguration: const BetterPlayerControlsConfiguration(
+          showControls: false,
+        ),
+      ),
+    );
+
+    Future.delayed(const Duration(milliseconds: 50), () {
+      if (!mounted || globalVideoController == null) return;
+
+      try {
+        final dataSource = BetterPlayerDataSource(
+          BetterPlayerDataSourceType.network,
+          url,
+          videoFormat: BetterPlayerVideoFormat.other,
+        );
+
+        globalVideoController!.setupDataSource(dataSource).then((_) {
+          if (mounted && globalVideoController != null) {
+            _safeControllerOperation(() {
+              globalVideoController!.play();
+            });
+          }
+        }).catchError((error) {
+          globalVideoController = null;
+        });
+      } catch (e) {
+        globalVideoController = null;
+      }
+    });
+  }
+
+  @override
+  void dispose() {
+    _pageController.dispose();
+    // 不在这里 dispose 全局控制器
+    super.dispose();
+  }
+
   bool _isVisible = false;
 
   void _onVisibilityChanged(VisibilityInfo info) {
-    final visible = info.visibleFraction > 0.5; // 页面超过一半可见就算“可见”
+    if (!mounted) return;
+    final visible = info.visibleFraction > 0.5;
     if (_isVisible != visible) {
       setState(() {
         _isVisible = visible;
       });
     }
     if (visible) {
-      // 页面可见时恢复播放
       consoleLog("页面可见");
+      _safeControllerOperation(() {
+        globalVideoController!.play();
+      });
     } else {
-      // 页面不可见时暂停播放
       consoleLog("页面不可见");
+      _safeControllerOperation(() {
+        globalVideoController!.pause();
+      });
+    }
+  }
+
+  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);
+      }
     }
   }
 
@@ -55,6 +158,24 @@ class _VideoRecommendListPageState extends ConsumerState<VideoRecommendListPage>
   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,
@@ -63,15 +184,24 @@ class _VideoRecommendListPageState extends ConsumerState<VideoRecommendListPage>
           horizontal: horizontalPadding,
           vertical: horizontalPadding,
         ),
-        child: PageView(
-          pageSnapping: true,
+        child: PageView.builder(
+          controller: _pageController,
+          onPageChanged: _onPageChanged,
           scrollDirection: Axis.vertical,
-          children: List.generate(videos.length, (index) {
+          itemCount: videos.length,
+          itemBuilder: (context, index) {
+            // 添加边界检查
+            if (index < 0 || index >= videos.length) {
+              return const SizedBox.shrink();
+            }
+            final item = videos[index];
             return VideoPlayItemWidget(
-              item: videos[index],
-              isActive: _isVisible,
+              key: ValueKey("video_${item.contentId ?? index}"),
+              item: item,
+              isActive: _isVisible && _currentPageIndex == index,
+              index: index,
             );
-          }),
+          },
         ),
       ),
     );