| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472 |
- import 'dart:io';
- import 'package:better_player_plus/better_player_plus.dart';
- import 'package:flutter/material.dart';
- 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/constant/config.dart';
- import 'package:news_app/constant/size_res.dart';
- import 'package:news_app/extension/base.dart';
- import 'package:news_app/model/video_new_model.dart';
- import 'package:news_app/ui/video/comment_page.dart';
- import 'package:news_app/ui/video/video_recommend_list_page.dart';
- import 'package:news_app/util/device_util.dart';
- import 'package:news_app/util/log.util.dart';
- import 'package:news_app/util/share_util.dart';
- import 'package:news_app/widget/my_txt.dart';
- import '../../constant/color_res.dart';
- import '../../provider/video_detail_provider.dart';
- import '../../util/theme_util.dart';
- import '../../widget/auth_gesture_detector.dart';
- import '../../widget/load_image.dart';
- /// @author: bo.zeng
- /// @email: cnhbwds@gmail.com
- /// @date: 2025 2025/4/9 16:00
- /// @description:
- class VideoParam {
- final String id;
- final String? videoUrl;
- VideoParam({required this.id, this.videoUrl});
- }
- class VideoDetailPage extends ConsumerStatefulWidget {
- final VideoParam param;
- const VideoDetailPage({super.key, required this.param});
- @override
- ConsumerState<VideoDetailPage> createState() => _VideoDetailPageState();
- }
- final videoDetailProvider =
- NotifierProvider<VideoDetailProvider, VideoNewModel>(
- () => VideoDetailProvider(),
- );
- class _VideoDetailPageState extends ConsumerState<VideoDetailPage>
- with WidgetsBindingObserver {
- BetterPlayerController? _betterPlayerController;
- ProviderSubscription? subscription;
- bool _isWeChatInstalled = false;
- Future<void> _checkWeChatInstallation() async {
- if (Platform.isAndroid) {
- final installed = await isWeChatInstalledOnlyAndroid();
- setState(() => _isWeChatInstalled = installed);
- } else if (Platform.isIOS) {
- final installed = await fluwx.isWeChatInstalled;
- setState(() => _isWeChatInstalled = installed);
- }
- }
- Future<void> shareAction(VideoNewModel data) async {
- showModalBottomSheet(
- context: context,
- builder:
- (context) => SafeArea(
- child: Column(
- mainAxisSize: MainAxisSize.min,
- mainAxisAlignment: MainAxisAlignment.spaceBetween,
- children: [
- SizedBox(height: 10.h),
- Container(
- 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,
- children: [
- if (_isWeChatInstalled)
- GestureDetector(
- onTap: () {
- Navigator.pop(context);
- shareWeiXinUrl(
- title: data.shareDesc ?? "",
- url: data.shareUrl ?? "",
- );
- },
- child: Container(
- width: 100.w,
- height: 80.h,
- child: Column(
- crossAxisAlignment: CrossAxisAlignment.center,
- mainAxisAlignment: MainAxisAlignment.center,
- children: [
- LoadAssetImage(
- 'share_wxhy',
- width: 40.w,
- height: 40.h,
- ),
- SizedBox(height: 10.h),
- myTxt(
- text: "微信好友",
- color: Colors.black,
- fontSize: 12.sp,
- fontWeight: FontWeight.bold,
- ),
- ],
- ),
- ),
- ),
- if (_isWeChatInstalled)
- GestureDetector(
- onTap: () {
- Navigator.pop(context);
- shareWeiXinPYUrl(
- title: data.shareDesc ?? "",
- url: data.shareUrl ?? "",
- );
- },
- child: Container(
- width: 100.w,
- height: 80.h,
- child: Column(
- mainAxisAlignment: MainAxisAlignment.center,
- children: [
- LoadAssetImage(
- 'share_pyq',
- width: 40.w,
- height: 40.h,
- ),
- SizedBox(height: 10.h),
- myTxt(
- text: "朋友圈",
- color: Colors.black,
- fontSize: 12.sp,
- fontWeight: FontWeight.bold,
- ),
- ],
- ),
- ),
- ),
- GestureDetector(
- onTap: () {
- Navigator.pop(context);
- shareUrl(
- title: data.shareDesc ?? "",
- url: data.shareUrl ?? "",
- );
- },
- child: Container(
- width: 100.w,
- height: 80.h,
- child: Column(
- mainAxisAlignment: MainAxisAlignment.center,
- children: [
- LoadAssetImage(
- 'share_xtfx',
- width: 40.w,
- height: 40.h,
- ),
- SizedBox(height: 10.h),
- myTxt(
- text: "系统分享",
- color: Colors.black,
- fontSize: 12.sp,
- fontWeight: FontWeight.bold,
- ),
- ],
- ),
- ),
- ),
- ],
- ),
- ),
- ],
- ),
- ),
- );
- }
- @override
- void dispose() {
- _betterPlayerController?.pause();
- _betterPlayerController?.dispose();
- subscription?.close();
- super.dispose();
- }
- @override
- void initState() {
- super.initState();
- consoleLog("VideoDetailPage: initState called, param.id = ${widget.param.id}");
- setImmersiveStatusBar();
- _checkWeChatInstallation();
- ref.read(videoDetailProvider.notifier).fetchVideoDetail(widget.param.id);
- if (widget.param.videoUrl?.isNotEmpty == true) {
- consoleLog("VideoDetailPage: videoUrl from param = ${widget.param.videoUrl}");
- _initPlayer(widget.param.videoUrl);
- } else {
- consoleLog("VideoDetailPage: videoUrl not in param, waiting for provider");
- subscription = ref.listenManual(videoDetailProvider, (pre, next) {
- consoleLog("VideoDetailPage: provider updated, url = ${next.url}");
- if (pre?.url != next.url && next.url?.isNotEmpty == true) {
- _initPlayer(next.url);
- }
- });
- }
- // 监听 App 生命周期变化
- WidgetsBinding.instance.addObserver(this);
- }
- @override
- void didChangeAppLifecycleState(AppLifecycleState state) {
- super.didChangeAppLifecycleState(state);
- if (_betterPlayerController == null) return;
- if (state == AppLifecycleState.inactive ||
- state == AppLifecycleState.paused) {
- // App进入后台或锁屏,暂停播放
- _betterPlayerController?.pause();
- } else if (state == AppLifecycleState.resumed) {
- // App回到前台(是否继续播放可根据需求选择)如果希望回到前台自动播放,就取消注释
- // _betterPlayerController?.play();
- }
- }
- void _initPlayer(String? url) {
- consoleLog("VideoDetailPage: _initPlayer called with url: $url");
- if (url == null || url.isEmpty) {
- consoleLog("VideoDetailPage: url is null or empty, returning");
- return;
- }
- _betterPlayerController?.dispose(); // dispose 旧的
- consoleLog("VideoDetailPage: creating BetterPlayerDataSource");
- final dataSource = BetterPlayerDataSource(
- BetterPlayerDataSourceType.network,
- url,
- videoFormat: BetterPlayerVideoFormat.other, // 避免格式识别失败
- );
- consoleLog("VideoDetailPage: creating BetterPlayerController");
- _betterPlayerController = BetterPlayerController(
- BetterPlayerConfiguration(
- errorBuilder: (context, errorMessage) {
- consoleLog("VideoDetailPage: errorBuilder called - $errorMessage");
- return Center(
- child: myTxt(text: "视频加载失败", color: Colors.white, fontSize: 14.sp),
- );
- },
- autoPlay: true,
- aspectRatio: 9 / 16, // 使用竖屏宽高比
- fit: BoxFit.cover,
- controlsConfiguration: const BetterPlayerControlsConfiguration(
- showControls: false,
- ),
- handleLifecycle: false,
- ),
- betterPlayerDataSource: dataSource,
- );
- // 添加播放器事件监听
- _betterPlayerController!.addEventsListener((event) {
- consoleLog("VideoDetailPage: BetterPlayerEvent: ${event.betterPlayerEventType}");
- if (event.betterPlayerEventType == BetterPlayerEventType.play) {
- consoleLog("VideoDetailPage: Video started playing");
- } else if (event.betterPlayerEventType == BetterPlayerEventType.exception) {
- consoleLog("VideoDetailPage: Exception occurred, attempting to replay...");
- _betterPlayerController?.pause();
- Future.delayed(const Duration(milliseconds: 500), () {
- _betterPlayerController?.play();
- });
- } else if (event.betterPlayerEventType == BetterPlayerEventType.bufferingStart) {
- consoleLog("VideoDetailPage: Buffering started");
- } else if (event.betterPlayerEventType == BetterPlayerEventType.bufferingEnd) {
- consoleLog("VideoDetailPage: Buffering ended");
- }
- });
- consoleLog("VideoDetailPage: BetterPlayerController created successfully");
- }
- @override
- Widget build(BuildContext context) {
- final video = ref.watch(videoDetailProvider);
- consoleLog("VideoDetailPage: build called, controller = $_betterPlayerController, video.url = ${video.url}");
- return Material(
- color: Colors.black,
- child: Stack(
- fit: StackFit.expand,
- alignment: Alignment.centerRight,
- children: [
- if (_betterPlayerController != null)
- Center(
- child: BetterPlayer(controller: _betterPlayerController!),
- )
- else
- Center(
- child: myTxt(
- text: "视频加载中...",
- color: Colors.white,
- fontSize: 14.sp,
- ),
- ),
- Positioned(
- left: 20.w,
- top: 54.h,
- child: GestureDetector(
- onTap: () => context.pop(),
- child: Icon(Icons.arrow_back_ios, color: Colors.white),
- ),
- ),
- Positioned(
- right: 20.h,
- bottom: 150.h,
- child: Column(
- mainAxisAlignment: MainAxisAlignment.center,
- children: [
- SizedBox(height: 100.h),
- AuthGestureDetector(
- onTap: () {
- ref
- .read(videoDetailProvider.notifier)
- .likeVideo(video.isLiked ?? false);
- ref
- .read(recommendListProvider.notifier)
- .fetchVideoLike(
- videoId: widget.param.id,
- current: video.isLiked ?? false,
- );
- },
- child: Icon(
- Icons.favorite,
- color: video.isLiked == true ? Colors.red : Colors.white,
- size: 25.sp,
- ),
- ),
- myTxt(
- text: video.likeCount.toSafeString,
- color: Colors.white,
- fontSize: 12.sp,
- ),
- SizedBox(height: 15.h),
- AuthGestureDetector(
- onTap: () {
- String? contentId = video.contentId;
- String? title = video.title;
- //1.活动中的视频 2.视频Tab进来的普通视频
- _showBottomCommentDialog(
- context,
- contentId,
- title,
- CommentType.video,
- );
- },
- child: Icon(Icons.message, color: Colors.white, size: 25.sp),
- ),
- myTxt(
- text: video.commentCount.toSafeString,
- color: Colors.white,
- fontSize: 12.sp,
- ),
- SizedBox(height: 15.h),
- AuthGestureDetector(
- onTap: () {
- ref
- .read(videoDetailProvider.notifier)
- .favoriteVideo(video.isFavorite ?? false);
- ref
- .read(recommendListProvider.notifier)
- .fetchVideoFavorite(
- videoId: widget.param.id,
- current: video.isFavorite ?? false,
- );
- },
- child: Icon(
- Icons.star,
- color: video.isFavorite == true ? Colors.red : Colors.white,
- size: 25.sp,
- ),
- ),
- myTxt(
- text: video.favoriteCount.toSafeString,
- color: Colors.white,
- fontSize: 12.sp,
- ),
- SizedBox(height: 15.h),
- GestureDetector(
- onTap: () {
- // shareUrl(
- // title: video.shareDesc ?? "",
- // url: video.shareUrl ?? "",
- // );
- shareAction(video);
- if (uuid.isNotEmpty) {
- ref
- .read(recommendListProvider.notifier)
- .fetchVideoShare(contentId: widget.param.id);
- }
- },
- child: Icon(
- Icons.screen_share,
- color: Colors.white,
- size: 25.sp,
- ),
- ),
- myTxt(
- text: video.shareCount.toSafeString,
- color: Colors.white,
- fontSize: 12.sp,
- ),
- SizedBox(height: 15.h),
- ],
- ),
- ),
- ],
- ),
- );
- }
- void _showBottomCommentDialog(
- BuildContext ctx,
- String? contentId,
- String? title,
- CommentType commentType,
- ) {
- showModalBottomSheet(
- useSafeArea: true,
- backgroundColor: Colors.white,
- context: ctx,
- isScrollControlled: true,
- // 允许内容滚动并控制高度
- builder: (context) {
- return Container(
- padding: EdgeInsets.all(horizontalPadding),
- height: screenHeight * 0.7,
- child: Column(
- crossAxisAlignment: CrossAxisAlignment.start,
- children: [
- SizedBox(height: 15.h),
- myTxt(text: title ?? "", color: color333333, fontSize: 15.sp),
- Container(
- height: 0.5,
- width: screenWidth,
- color: colorE5E5E5,
- margin: EdgeInsets.symmetric(vertical: 10.h),
- ),
- Expanded(
- child: CommentPage(
- type: commentType,
- articleId: contentId ?? "",
- ),
- ),
- ],
- ),
- );
- },
- );
- }
- }
|