| 
									
										
										
										
											2025-07-13 14:01:29 +03:00
										 |  |  | 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'; | 
					
						
							| 
									
										
										
										
											2025-10-03 09:38:45 +00:00
										 |  |  | import 'package:neomovies_mobile/data/models/player/subtitle.dart' as local_subtitle; | 
					
						
							| 
									
										
										
										
											2025-07-13 14:01:29 +03:00
										 |  |  | 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 = []; | 
					
						
							| 
									
										
										
										
											2025-10-03 09:38:45 +00:00
										 |  |  |   List<local_subtitle.Subtitle> _subtitles = []; | 
					
						
							| 
									
										
										
										
											2025-07-13 14:01:29 +03:00
										 |  |  |    | 
					
						
							|  |  |  |   // Selected options
 | 
					
						
							|  |  |  |   VideoSource? _selectedSource; | 
					
						
							|  |  |  |   VideoQuality? _selectedQuality; | 
					
						
							|  |  |  |   AudioTrack? _selectedAudioTrack; | 
					
						
							| 
									
										
										
										
											2025-10-03 09:38:45 +00:00
										 |  |  |   local_subtitle.Subtitle? _selectedSubtitle; | 
					
						
							| 
									
										
										
										
											2025-07-13 14:01:29 +03:00
										 |  |  |    | 
					
						
							|  |  |  |   // 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; | 
					
						
							| 
									
										
										
										
											2025-10-03 09:38:45 +00:00
										 |  |  |   List<local_subtitle.Subtitle> get subtitles => _subtitles; | 
					
						
							| 
									
										
										
										
											2025-07-13 14:01:29 +03:00
										 |  |  |   VideoSource? get selectedSource => _selectedSource; | 
					
						
							|  |  |  |   VideoQuality? get selectedQuality => _selectedQuality; | 
					
						
							|  |  |  |   AudioTrack? get selectedAudioTrack => _selectedAudioTrack; | 
					
						
							| 
									
										
										
										
											2025-10-03 09:38:45 +00:00
										 |  |  |   local_subtitle.Subtitle? get selectedSubtitle => _selectedSubtitle; | 
					
						
							| 
									
										
										
										
											2025-07-13 14:01:29 +03:00
										 |  |  |   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, | 
					
						
							| 
									
										
										
										
											2025-10-03 09:38:45 +00:00
										 |  |  |     List<local_subtitle.Subtitle>? subtitles, | 
					
						
							| 
									
										
										
										
											2025-07-13 14:01:29 +03:00
										 |  |  |   }) 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
 | 
					
						
							| 
									
										
										
										
											2025-10-03 09:38:45 +00:00
										 |  |  |   void setSubtitle(local_subtitle.Subtitle subtitle) { | 
					
						
							| 
									
										
										
										
											2025-07-13 14:01:29 +03:00
										 |  |  |     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(); | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  | } |