import 'dart:io'; import 'dart:async'; import 'package:flutter/material.dart'; import 'package:media_kit/media_kit.dart'; import 'package:media_kit_video/media_kit_video.dart'; import 'package:flutter_cache_manager/flutter_cache_manager.dart'; import 'package:path/path.dart' as path; import '../../main.dart'; class VideoPlayerWidget extends StatefulWidget { final String videoUrl; const VideoPlayerWidget({super.key, required this.videoUrl}); @override State createState() => _VideoPlayerWidgetState(); } class _VideoPlayerWidgetState extends State { late final Player _player; late final VideoController _controller; bool _isInitialized = false; bool _hasError = false; bool _usingCache = false; double _downloadProgress = 0; double _bufferingProgress = 0; bool _isBuffering = false; File? _cachedFile; StreamSubscription? _cacheSubscription; @override void initState() { super.initState(); _initializePlayer(); } Future _initializePlayer() async { try { _cachedFile = await videoCacheManager.getSingleFile(widget.videoUrl); _usingCache = _cachedFile != null; _player = Player(); _controller = VideoController(_player); final source = _cachedFile != null ? Media(_cachedFile!.path, httpHeaders: {}) : Media(widget.videoUrl); await _player.open(source); //await _player.setVolume(0); if (_cachedFile == null) { _cacheVideo(); } _player.stream.error.listen(_handlePlayerError); _player.stream.buffering.listen(_handleBufferingUpdate); } catch (e) { _handleError("Player init error: $e"); } } void _handleBufferingUpdate(bool isBuffering) { setState(() { _isBuffering = isBuffering; _bufferingProgress = 0; if (!isBuffering && !_isInitialized && !_hasError) { _isInitialized = true; } }); } void _handlePlayerError(String error) { _handleError("Player error: ${error})"); } void _handleError(String message) { print(message); if (!_hasError) setState(() => _hasError = true); } Future _cacheVideo() async { final fileStream = videoCacheManager.getFileStream( widget.videoUrl, withProgress: true, ); _cacheSubscription = fileStream.listen((fileResponse) { if (fileResponse is DownloadProgress) { setState(() => _downloadProgress = fileResponse.progress ?? 0); } else if (fileResponse is FileInfo) { setState(() { _cachedFile = fileResponse.file; _usingCache = true; _downloadProgress = 0; }); } }, onError: (e) => print("Cache error: $e")); } void _retryPlayback() { setState(() { _hasError = false; _isInitialized = false; _downloadProgress = 0; _bufferingProgress = 0; _cacheSubscription?.cancel(); }); _initializePlayer(); } @override Widget build(BuildContext context) { return GestureDetector( child: Stack( children: [ // Video display if (_isInitialized) Video(controller: _controller), // Loading/buffering states if (!_isInitialized || _isBuffering) _buildLoadingState(), // Error state if (_hasError) _buildErrorState(), // Cache progress indicator if (_downloadProgress > 0 && _downloadProgress < 1) _buildCacheProgress(), // Buffering indicator if (_isBuffering && _isInitialized) _buildBufferingOverlay(), ], ), ); } Widget _buildLoadingState() { return Container( color: Colors.black, child: Center( child: Column( mainAxisSize: MainAxisSize.min, children: [ CircularProgressIndicator( value: _isBuffering ? _bufferingProgress : null, valueColor: const AlwaysStoppedAnimation(Colors.white), ), const SizedBox(height: 16), Text( _usingCache ? 'Loading cached video' : 'Streaming video', style: const TextStyle(color: Colors.white), ), ], ), ), ); } Widget _buildBufferingOverlay() { return Container( color: Colors.black54, child: Center( child: Column( mainAxisSize: MainAxisSize.min, children: [ CircularProgressIndicator( value: _bufferingProgress, valueColor: const AlwaysStoppedAnimation(Colors.white), ), const SizedBox(height: 8), const Text('Buffering...', style: TextStyle(color: Colors.white)), ], ), ), ); } Widget _buildCacheProgress() { return Positioned( bottom: 16, right: 16, child: Container( padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 8), decoration: BoxDecoration( color: Colors.black.withOpacity(0.7), borderRadius: BorderRadius.circular(20), ), child: Row( children: [ const Icon(Icons.download, color: Colors.white, size: 20), const SizedBox(width: 8), Text( '${(_downloadProgress * 100).toStringAsFixed(0)}%', style: const TextStyle(color: Colors.white), ), ], ), ), ); } Widget _buildErrorState() { return Container( color: Colors.black, child: Center( child: Column( mainAxisSize: MainAxisSize.min, children: [ const Icon(Icons.videocam_off, size: 64, color: Colors.white54), const SizedBox(height: 16), const Text('Playback Failed', style: TextStyle(color: Colors.white, fontSize: 18)), const SizedBox(height: 8), Text( _usingCache ? 'Cached file may be corrupted' : 'Network issue', style: const TextStyle(color: Colors.white70), ), const SizedBox(height: 16), Row( mainAxisAlignment: MainAxisAlignment.center, children: [ ElevatedButton.icon( icon: const Icon(Icons.refresh), label: const Text('Retry'), onPressed: _retryPlayback, ), const SizedBox(width: 16), if (!_usingCache) ElevatedButton.icon( icon: const Icon(Icons.download), label: const Text('Download'), onPressed: _cacheVideo, ), ], ) ], ), ), ); } @override void dispose() { _player.dispose(); _cacheSubscription?.cancel(); super.dispose(); } }