import 'dart:async'; import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; import 'package:video_player/video_player.dart'; import 'package:chewie/chewie.dart'; import 'package:neomovies_mobile/data/models/player/video_source.dart'; import 'package:neomovies_mobile/data/models/player/video_quality.dart'; import 'package:neomovies_mobile/data/models/player/audio_track.dart'; import 'package:neomovies_mobile/data/models/player/subtitle.dart' as local_subtitle; import 'package:neomovies_mobile/data/models/player/player_settings.dart'; class PlayerProvider with ChangeNotifier { // Controller instances VideoPlayerController? _videoPlayerController; ChewieController? _chewieController; // Player state bool _isInitialized = false; bool _isPlaying = false; bool _isBuffering = false; bool _isFullScreen = false; bool _showControls = true; Duration _position = Duration.zero; Duration _duration = Duration.zero; // Media info String? _mediaId; String? _mediaType; String? _title; String? _subtitle; String? _posterUrl; // Player settings PlayerSettings _settings; // Available options List _sources = []; List _qualities = []; List _audioTracks = []; List _subtitles = []; // Selected options VideoSource? _selectedSource; VideoQuality? _selectedQuality; AudioTrack? _selectedAudioTrack; local_subtitle.Subtitle? _selectedSubtitle; // Playback state double _volume = 1.0; bool _isMuted = false; double _playbackSpeed = 1.0; // Getters bool get isInitialized => _isInitialized; bool get isPlaying => _isPlaying; bool get isBuffering => _isBuffering; bool get isFullScreen => _isFullScreen; bool get showControls => _showControls; Duration get position => _position; Duration get duration => _duration; String? get mediaId => _mediaId; String? get mediaType => _mediaType; String? get title => _title; String? get subtitle => _subtitle; String? get posterUrl => _posterUrl; PlayerSettings get settings => _settings; List get sources => _sources; List get qualities => _qualities; List get audioTracks => _audioTracks; List get subtitles => _subtitles; VideoSource? get selectedSource => _selectedSource; VideoQuality? get selectedQuality => _selectedQuality; AudioTrack? get selectedAudioTrack => _selectedAudioTrack; local_subtitle.Subtitle? get selectedSubtitle => _selectedSubtitle; double get volume => _volume; bool get isMuted => _isMuted; double get playbackSpeed => _playbackSpeed; // Controllers VideoPlayerController? get videoPlayerController => _videoPlayerController; ChewieController? get chewieController => _chewieController; // Constructor PlayerProvider({PlayerSettings? initialSettings}) : _settings = initialSettings ?? PlayerSettings.defaultSettings(); // Initialize the player with media Future initialize({ required String mediaId, required String mediaType, String? title, String? subtitle, String? posterUrl, List? sources, List? qualities, List? audioTracks, List? subtitles, }) async { _mediaId = mediaId; _mediaType = mediaType; _title = title; _subtitle = subtitle; _posterUrl = posterUrl; // Set available options _sources = sources ?? []; _qualities = qualities ?? VideoQuality.defaultQualities; _audioTracks = audioTracks ?? []; _subtitles = subtitles ?? []; // Set default selections _selectedSource = _sources.isNotEmpty ? _sources.first : null; _selectedQuality = _qualities.isNotEmpty ? _qualities.first : null; _selectedAudioTrack = _audioTracks.isNotEmpty ? _audioTracks.first : null; _selectedSubtitle = _subtitles.firstWhere( (s) => s.id == 'none', orElse: () => _subtitles.first, ); // Initialize video player with the first source and quality if (_selectedSource != null && _selectedQuality != null) { await _initializeVideoPlayer(); } _isInitialized = true; notifyListeners(); } // Initialize video player with current source and quality Future _initializeVideoPlayer() async { if (_selectedSource == null || _selectedQuality == null) return; // Dispose of previous controllers if they exist await dispose(); try { // In a real app, you would fetch the actual video URL based on source and quality final videoUrl = _getVideoUrl(_selectedSource!, _selectedQuality!); _videoPlayerController = VideoPlayerController.networkUrl( Uri.parse(videoUrl), videoPlayerOptions: VideoPlayerOptions( mixWithOthers: true, ), ); await _videoPlayerController!.initialize(); // Setup position listener _videoPlayerController!.addListener(_videoPlayerListener); // Setup chewie controller _setupChewieController(); // Start playing if autoplay is enabled if (_settings.autoPlay) { await _videoPlayerController!.play(); _isPlaying = true; } notifyListeners(); } catch (e) { debugPrint('Error initializing video player: $e'); // Handle error appropriately rethrow; } } // Setup Chewie controller with custom options void _setupChewieController() { _chewieController = ChewieController( videoPlayerController: _videoPlayerController!, autoPlay: _settings.autoPlay, looping: false, allowFullScreen: true, allowMuting: true, allowPlaybackSpeedChanging: true, showControls: _settings.showControlsOnStart, showControlsOnInitialize: _settings.showControlsOnStart, placeholder: _posterUrl != null ? Image.network(_posterUrl!) : null, aspectRatio: _videoPlayerController!.value.aspectRatio, // Custom options can be added here ); // Listen to Chewie events _chewieController!.addListener(() { if (_chewieController!.isFullScreen != _isFullScreen) { _isFullScreen = _chewieController!.isFullScreen; notifyListeners(); } if (_chewieController!.isPlaying != _isPlaying) { _isPlaying = _chewieController!.isPlaying; notifyListeners(); } }); } // Video player listener void _videoPlayerListener() { if (!_videoPlayerController!.value.isInitialized) return; final controller = _videoPlayerController!; // Update buffering state final isBuffering = controller.value.isBuffering; if (_isBuffering != isBuffering) { _isBuffering = isBuffering; notifyListeners(); } // Update position and duration if (controller.value.duration != _duration) { _duration = controller.value.duration; } if (controller.value.position != _position) { _position = controller.value.position; notifyListeners(); } } // Get video URL based on source and quality // In a real app, this would make an API call to get the stream URL String _getVideoUrl(VideoSource source, VideoQuality quality) { // This is a placeholder - replace with actual logic to get the video URL return 'https://example.com/stream/$mediaType/$mediaId?source=${source.name.toLowerCase()}&quality=${quality.name}'; } // Toggle play/pause Future togglePlayPause() async { if (_videoPlayerController == null) return; if (_isPlaying) { await _videoPlayerController!.pause(); } else { await _videoPlayerController!.play(); } _isPlaying = !_isPlaying; notifyListeners(); } // Seek to a specific position Future seekTo(Duration position) async { if (_videoPlayerController == null) return; await _videoPlayerController!.seekTo(position); _position = position; notifyListeners(); } // Set volume (0.0 to 1.0) Future setVolume(double volume) async { if (_videoPlayerController == null) return; _volume = volume.clamp(0.0, 1.0); await _videoPlayerController!.setVolume(_isMuted ? 0.0 : _volume); notifyListeners(); } // Toggle mute Future toggleMute() async { if (_videoPlayerController == null) return; _isMuted = !_isMuted; await _videoPlayerController!.setVolume(_isMuted ? 0.0 : _volume); notifyListeners(); } // Set playback speed Future setPlaybackSpeed(double speed) async { if (_videoPlayerController == null) return; _playbackSpeed = speed; await _videoPlayerController!.setPlaybackSpeed(speed); notifyListeners(); } // Change video source Future setSource(VideoSource source) async { if (_selectedSource == source) return; _selectedSource = source; await _initializeVideoPlayer(); notifyListeners(); } // Change video quality Future setQuality(VideoQuality quality) async { if (_selectedQuality == quality) return; _selectedQuality = quality; await _initializeVideoPlayer(); notifyListeners(); } // Change audio track void setAudioTrack(AudioTrack track) { if (_selectedAudioTrack == track) return; _selectedAudioTrack = track; // In a real implementation, you would update the audio track on the video player notifyListeners(); } // Change subtitle void setSubtitle(local_subtitle.Subtitle subtitle) { if (_selectedSubtitle == subtitle) return; _selectedSubtitle = subtitle; // In a real implementation, you would update the subtitle on the video player notifyListeners(); } // Toggle fullscreen void toggleFullScreen() { if (_chewieController == null) return; _isFullScreen = !_isFullScreen; if (_isFullScreen) { _chewieController!.enterFullScreen(); } else { _chewieController!.exitFullScreen(); } notifyListeners(); } // Toggle controls visibility void toggleControls() { _showControls = !_showControls; notifyListeners(); } // Update player settings void updateSettings(PlayerSettings newSettings) { _settings = newSettings; // Apply settings that affect the current playback if (_videoPlayerController != null) { _videoPlayerController!.setPlaybackSpeed(_settings.playbackSpeed); // Apply other settings as needed } notifyListeners(); } // Clean up resources @override Future dispose() async { _videoPlayerController?.removeListener(_videoPlayerListener); await _videoPlayerController?.dispose(); await _chewieController?.dispose(); _videoPlayerController = null; _chewieController = null; _isInitialized = false; _isPlaying = false; _isBuffering = false; _isFullScreen = false; _position = Duration.zero; _duration = Duration.zero; super.dispose(); } }