Files
neomovies-mobile/lib/presentation/providers/player/player_provider.dart
2025-07-13 14:01:29 +03:00

369 lines
11 KiB
Dart

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';
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<VideoSource> _sources = [];
List<VideoQuality> _qualities = [];
List<AudioTrack> _audioTracks = [];
List<Subtitle> _subtitles = [];
// Selected options
VideoSource? _selectedSource;
VideoQuality? _selectedQuality;
AudioTrack? _selectedAudioTrack;
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<VideoSource> get sources => _sources;
List<VideoQuality> get qualities => _qualities;
List<AudioTrack> get audioTracks => _audioTracks;
List<Subtitle> get subtitles => _subtitles;
VideoSource? get selectedSource => _selectedSource;
VideoQuality? get selectedQuality => _selectedQuality;
AudioTrack? get selectedAudioTrack => _selectedAudioTrack;
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<void> initialize({
required String mediaId,
required String mediaType,
String? title,
String? subtitle,
String? posterUrl,
List<VideoSource>? sources,
List<VideoQuality>? qualities,
List<AudioTrack>? audioTracks,
List<Subtitle>? 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<void> _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<void> togglePlayPause() async {
if (_videoPlayerController == null) return;
if (_isPlaying) {
await _videoPlayerController!.pause();
} else {
await _videoPlayerController!.play();
}
_isPlaying = !_isPlaying;
notifyListeners();
}
// Seek to a specific position
Future<void> seekTo(Duration position) async {
if (_videoPlayerController == null) return;
await _videoPlayerController!.seekTo(position);
_position = position;
notifyListeners();
}
// Set volume (0.0 to 1.0)
Future<void> 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<void> toggleMute() async {
if (_videoPlayerController == null) return;
_isMuted = !_isMuted;
await _videoPlayerController!.setVolume(_isMuted ? 0.0 : _volume);
notifyListeners();
}
// Set playback speed
Future<void> setPlaybackSpeed(double speed) async {
if (_videoPlayerController == null) return;
_playbackSpeed = speed;
await _videoPlayerController!.setPlaybackSpeed(speed);
notifyListeners();
}
// Change video source
Future<void> setSource(VideoSource source) async {
if (_selectedSource == source) return;
_selectedSource = source;
await _initializeVideoPlayer();
notifyListeners();
}
// Change video quality
Future<void> 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(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<void> 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();
}
}