import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; import 'package:flutter_screenutil/flutter_screenutil.dart'; import 'package:onufitness/screens/u_vault/controllers/uvault_video_controller.dart'; import 'package:onufitness/utils/helper_function.dart'; import 'package:shimmer/shimmer.dart'; import 'package:video_player/video_player.dart'; import 'package:chewie/chewie.dart'; import 'package:get/get.dart'; import 'package:onufitness/constants/color_constant.dart'; class VideoCard extends StatefulWidget { final String? title; final String category; final String videoUrl; final String? videoId; final String? thumbnailUrl; final bool isMyVideoScreen; final VoidCallback? onPressed; const VideoCard({ super.key, this.title, required this.category, required this.videoUrl, this.videoId, this.thumbnailUrl, required this.isMyVideoScreen, this.onPressed, }); @override State createState() => _VideoCardState(); } class _VideoCardState extends State { final controller = Get.find(); ChewieController? _chewieController; VideoPlayerController? _videoPlayerController; bool _isVideoInitialized = false; bool _isVideoPlaying = false; bool _isInitializing = false; bool _isDisposed = false; Future _initializeVideo() async { if (_isInitializing || _isVideoInitialized || _isDisposed) return; setState(() { _isInitializing = true; }); try { controller.setCurrentPlayingVideo(widget.videoUrl); _videoPlayerController = VideoPlayerController.networkUrl( Uri.parse(widget.videoUrl), ); await _videoPlayerController!.initialize(); if (_isDisposed || !mounted) { await _videoPlayerController!.dispose(); return; } await _videoPlayerController!.setVolume(1.0); _chewieController = ChewieController( // Add Android-specific Color customization materialProgressColors: ChewieProgressColors( playedColor: Color(primaryColor), handleColor: Color(primaryColor), backgroundColor: Colors.grey, bufferedColor: Colors.grey.shade300, ), // Add iOS-specific Color customization cupertinoProgressColors: ChewieProgressColors( playedColor: Color(primaryColor), handleColor: Color(primaryColor), backgroundColor: Colors.grey, bufferedColor: Colors.grey.shade300, ), videoPlayerController: _videoPlayerController!, autoPlay: true, looping: false, showControls: true, aspectRatio: _videoPlayerController!.value.aspectRatio, deviceOrientationsAfterFullScreen: [DeviceOrientation.portraitUp], errorBuilder: (context, errorMessage) { return Center( child: Column( mainAxisAlignment: MainAxisAlignment.center, children: [ Icon(Icons.error, color: Colors.red, size: 40.sp), SizedBox(height: 8.h), Text( 'Video playback error', style: TextStyle(color: Colors.red, fontSize: 12.sp), ), ], ), ); }, ); if (mounted && !_isDisposed) { setState(() { _isVideoInitialized = true; _isVideoPlaying = true; _isInitializing = false; }); } else { // Clean up if component was disposed during initialization await _disposeVideoControllers(); } } catch (e) { if (mounted && !_isDisposed) { setState(() { _isInitializing = false; }); } await _disposeVideoControllers(); } } Future _disposeVideoControllers() async { if (_chewieController != null) { _chewieController!.dispose(); _chewieController!.pause(); _chewieController = null; } if (_videoPlayerController != null) { await _videoPlayerController!.dispose(); await _videoPlayerController!.pause(); _videoPlayerController = null; } if (mounted && !_isDisposed) { setState(() { _isVideoInitialized = false; _isVideoPlaying = false; _isInitializing = false; }); } } void _onThumbnailTap() { if (!_isInitializing && !_isVideoInitialized) { _initializeVideo(); } } @override void dispose() { _isDisposed = true; _disposeVideoControllers(); super.dispose(); } Widget _buildThumbnailView() { return Stack( children: [ // Thumbnail Image if (widget.thumbnailUrl != null && widget.thumbnailUrl!.isNotEmpty) Image.network( widget.thumbnailUrl ?? "https://placehold.co/400?text=Thumbnail&font=roboto", height: isTablet ? 250.h : 200.h, width: double.infinity, fit: BoxFit.cover, loadingBuilder: (context, child, loadingProgress) { if (loadingProgress == null) return child; return _buildShimmerPlaceholder(); }, errorBuilder: (context, error, stackTrace) { return _buildErrorPlaceholder(); }, ) else _buildErrorPlaceholder(), // Play Button Overlay Positioned.fill( child: Container( color: Colors.black26, child: Center( child: GestureDetector( onTap: _onThumbnailTap, child: Container( width: 60.w, height: 60.h, decoration: BoxDecoration( color: Colors.black54, shape: BoxShape.circle, ), child: Icon( Icons.play_arrow, color: Colors.white, size: 30.sp, ), ), ), ), ), ), // Loading indicator when initializing if (_isInitializing) Positioned.fill( child: Container( color: Colors.black26, child: Center( child: CircularProgressIndicator( valueColor: AlwaysStoppedAnimation(Colors.white), ), ), ), ), ], ); } Widget _buildVideoView() { if (_chewieController == null) { return Container( height: isTablet ? 250.h : 200, width: double.infinity, color: Colors.black, child: Center( child: CircularProgressIndicator( valueColor: AlwaysStoppedAnimation(Colors.white), ), ), ); } // Calculate proper height based on aspect ratio final aspectRatio = _videoPlayerController?.value.aspectRatio ?? 16 / 9; final videoHeight = MediaQuery.of(context).size.width / aspectRatio; // Set minimum and maximum heights to prevent extreme sizes final minHeight = 200.h; final maxHeight = 300.h; final finalHeight = videoHeight.clamp(minHeight, maxHeight); return SizedBox( height: finalHeight, width: double.infinity, child: AspectRatio( aspectRatio: aspectRatio, child: Chewie(controller: _chewieController!), ), ); } Widget _buildShimmerPlaceholder() { return Shimmer.fromColors( baseColor: Colors.grey.shade300, highlightColor: Colors.grey.shade100, child: Container( height: isTablet ? 250.h : 200, width: double.infinity, color: Colors.grey.shade300, ), ); } Widget _buildErrorPlaceholder() { return Container( height: isTablet ? 250.h : 200, width: double.infinity, color: Colors.grey.shade200, child: Column( mainAxisAlignment: MainAxisAlignment.center, children: [ Icon( Icons.video_library_outlined, size: 40.sp, color: Colors.grey.shade500, ), SizedBox(height: 8.h), ], ), ); } @override Widget build(BuildContext context) { return Container( decoration: BoxDecoration(color: Colors.white), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ ClipRRect( // borderRadius: BorderRadius.vertical(top: Radius.circular(12.r)), child: _isVideoInitialized && _isVideoPlaying && _chewieController != null && !_isDisposed ? _buildVideoView() : _buildThumbnailView(), ), widget.title != null ? Padding( padding: EdgeInsets.all(8.0.w), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( widget.category, style: TextStyle(fontSize: 14.sp, color: greyTextColor1), ), SizedBox(height: 4.h), Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ Expanded( child: Text( widget.title!, maxLines: 2, overflow: TextOverflow.ellipsis, style: TextStyle( fontSize: 15.sp, color: Colors.black, fontWeight: FontWeight.w600, ), ), ), if (widget.isMyVideoScreen) IconButton( onPressed: widget.onPressed, icon: Icon(Icons.more_vert), ), ], ), ], ), ) : Container(), ], ), ); } } //.........Above one is Chewie Player...................................................................................................... //.........Below one is Better Player...................................................................................................... // import 'dart:developer'; // import 'package:flutter/material.dart'; // import 'package:flutter/services.dart'; // import 'package:flutter_screenutil/flutter_screenutil.dart'; // import 'package:onufitness/screens/u_vault/controllers/uvault_video_controller.dart'; // import 'package:shimmer/shimmer.dart'; // import 'package:better_player_plus/better_player_plus.dart'; // import 'package:get/get.dart'; // import 'package:onufitness/constants/color_constant.dart'; // class VideoCard extends StatefulWidget { // final String title; // final String category; // final String videoUrl; // final String? videoId; // final String? thumbnailUrl; // final bool isMyVideoScreen; // final VoidCallback? onPressed; // const VideoCard({ // super.key, // required this.title, // required this.category, // required this.videoUrl, // this.videoId, // this.thumbnailUrl, // required this.isMyVideoScreen, // this.onPressed, // }); // @override // State createState() => _VideoCardState(); // } // class _VideoCardState extends State { // final controller = Get.find(); // BetterPlayerController? _betterPlayerController; // bool _isVideoInitialized = false; // bool _isVideoPlaying = false; // bool _isInitializing = false; // bool _isDisposed = false; // Future _initializeVideo() async { // if (_isInitializing || _isVideoInitialized || _isDisposed) return; // setState(() { // _isInitializing = true; // }); // try { // controller.setCurrentPlayingVideo(widget.videoUrl); // // Create BetterPlayer configuration // BetterPlayerConfiguration betterPlayerConfiguration = // BetterPlayerConfiguration( // aspectRatio: 16 / 9, // fit: BoxFit.contain, // autoPlay: true, // looping: false, // fullScreenByDefault: false, // allowedScreenSleep: false, // deviceOrientationsAfterFullScreen: [DeviceOrientation.portraitUp], // controlsConfiguration: BetterPlayerControlsConfiguration( // showControls: true, // enableOverflowMenu: false, // enableFullscreen: true, // enablePip: false, // enablePlayPause: true, // enableMute: true, // enableProgressBar: true, // controlBarColor: Colors.black.withValues(alpha: 0.5), // progressBarPlayedColor: Color(primaryColor), // progressBarHandleColor: Color(primaryColor), // progressBarBufferedColor: Colors.grey.shade300, // progressBarBackgroundColor: Colors.grey, // iconsColor: Colors.white, // ), // errorBuilder: (context, errorMessage) { // return Center( // child: Column( // mainAxisAlignment: MainAxisAlignment.center, // children: [ // Icon(Icons.error, color: Colors.red, size: 40.sp), // SizedBox(height: 8.h), // Text( // 'Video playback error', // style: TextStyle(color: Colors.red, fontSize: 12.sp), // ), // ], // ), // ); // }, // ); // // Create data source // BetterPlayerDataSource dataSource = BetterPlayerDataSource( // BetterPlayerDataSourceType.network, // widget.videoUrl, // videoFormat: BetterPlayerVideoFormat.other, // ); // // Initialize BetterPlayer // _betterPlayerController = BetterPlayerController( // betterPlayerConfiguration, // ); // await _betterPlayerController!.setupDataSource(dataSource); // if (_isDisposed || !mounted) { // // If disposed during initialization, clean up // _betterPlayerController?.dispose(); // return; // } // // Set volume // await _betterPlayerController!.setVolume(1.0); // if (mounted && !_isDisposed) { // setState(() { // _isVideoInitialized = true; // _isVideoPlaying = true; // _isInitializing = false; // }); // } else { // // Clean up if component was disposed during initialization // _disposeVideoController(); // } // } catch (e) { // log('Error initializing video: $e'); // if (mounted && !_isDisposed) { // setState(() { // _isInitializing = false; // }); // } // _disposeVideoController(); // } // } // void _disposeVideoController() { // if (_betterPlayerController != null) { // _betterPlayerController!.dispose(); // _betterPlayerController = null; // } // if (mounted && !_isDisposed) { // setState(() { // _isVideoInitialized = false; // _isVideoPlaying = false; // _isInitializing = false; // }); // } // } // void _onThumbnailTap() { // if (!_isInitializing && !_isVideoInitialized) { // _initializeVideo(); // } // log(widget.videoUrl); // } // @override // void dispose() { // _isDisposed = true; // // Clean up video controller // _disposeVideoController(); // super.dispose(); // } // Widget _buildThumbnailView() { // return Stack( // children: [ // // Thumbnail Image // if (widget.thumbnailUrl != null && widget.thumbnailUrl!.isNotEmpty) // Image.network( // widget.thumbnailUrl ?? // "https://placehold.co/400?text=Thumbnail&font=roboto", // height: 180.h, // width: double.infinity, // fit: BoxFit.cover, // loadingBuilder: (context, child, loadingProgress) { // if (loadingProgress == null) return child; // return _buildShimmerPlaceholder(); // }, // errorBuilder: (context, error, stackTrace) { // return _buildErrorPlaceholder(); // }, // ) // else // _buildErrorPlaceholder(), // // Play Button Overlay // Positioned.fill( // child: Container( // color: Colors.black26, // child: Center( // child: GestureDetector( // onTap: _onThumbnailTap, // child: Container( // width: 60.w, // height: 60.h, // decoration: BoxDecoration( // color: Colors.black54, // shape: BoxShape.circle, // ), // child: Icon( // Icons.play_arrow, // color: Colors.white, // size: 30.sp, // ), // ), // ), // ), // ), // ), // // Loading indicator when initializing // if (_isInitializing) // Positioned.fill( // child: Container( // color: Colors.black26, // child: Center( // child: CircularProgressIndicator( // valueColor: AlwaysStoppedAnimation(Colors.white), // ), // ), // ), // ), // ], // ); // } // Widget _buildVideoView() { // if (_betterPlayerController == null) { // return Container( // height: 180.h, // width: double.infinity, // color: Colors.black, // child: Center( // child: CircularProgressIndicator( // valueColor: AlwaysStoppedAnimation(Colors.white), // ), // ), // ); // } // return SizedBox( // height: 180.h, // width: double.infinity, // child: BetterPlayer(controller: _betterPlayerController!), // ); // } // Widget _buildShimmerPlaceholder() { // return Shimmer.fromColors( // baseColor: Colors.grey.shade300, // highlightColor: Colors.grey.shade100, // child: Container( // height: 180.h, // width: double.infinity, // color: Colors.grey.shade300, // ), // ); // } // Widget _buildErrorPlaceholder() { // return Container( // height: 180.h, // width: double.infinity, // color: Colors.grey.shade200, // child: Column( // mainAxisAlignment: MainAxisAlignment.center, // children: [ // Icon( // Icons.video_library_outlined, // size: 40.sp, // color: Colors.grey.shade500, // ), // SizedBox(height: 8.h), // Text( // 'Video Thumbnail', // style: TextStyle(color: Colors.grey.shade500, fontSize: 12.sp), // ), // ], // ), // ); // } // @override // Widget build(BuildContext context) { // log("rebuild video card"); // return Container( // decoration: BoxDecoration( // color: Colors.white, // borderRadius: BorderRadius.circular(12.r), // boxShadow: [ // BoxShadow(color: Colors.black12, blurRadius: 4, spreadRadius: 2), // ], // ), // child: Column( // crossAxisAlignment: CrossAxisAlignment.start, // children: [ // ClipRRect( // borderRadius: BorderRadius.vertical(top: Radius.circular(12.r)), // child: // _isVideoInitialized && // _isVideoPlaying && // _betterPlayerController != null && // !_isDisposed // ? _buildVideoView() // : _buildThumbnailView(), // ), // Padding( // padding: EdgeInsets.all(8.0.w), // child: Column( // crossAxisAlignment: CrossAxisAlignment.start, // children: [ // Text( // widget.category, // style: TextStyle(fontSize: 14.sp, color: greyTextColor1), // ), // SizedBox(height: 4.h), // Row( // mainAxisAlignment: MainAxisAlignment.spaceBetween, // children: [ // Expanded( // child: Text( // widget.title, // maxLines: 2, // overflow: TextOverflow.ellipsis, // style: TextStyle( // fontSize: 15.sp, // color: Colors.black, // fontWeight: FontWeight.w600, // ), // ), // ), // if (widget.isMyVideoScreen) // IconButton( // onPressed: widget.onPressed, // icon: Icon(Icons.more_horiz), // ), // ], // ), // ], // ), // ), // ], // ), // ); // } // }