import 'package:audioplayers/audioplayers.dart'; import 'package:flutter/foundation.dart'; import '../models/spotify_track.dart'; class AudioService extends ChangeNotifier { static final AudioService _instance = AudioService._internal(); factory AudioService() => _instance; AudioService._internal() { _initializePlayer(); } final AudioPlayer _audioPlayer = AudioPlayer(); // Current track state SpotifyTrack? _currentTrack; bool _isPlaying = false; bool _isLoading = false; Duration _currentPosition = Duration.zero; Duration _totalDuration = Duration.zero; // Getters SpotifyTrack? get currentTrack => _currentTrack; bool get isPlaying => _isPlaying; bool get isLoading => _isLoading; Duration get currentPosition => _currentPosition; Duration get totalDuration => _totalDuration; double get progress => _totalDuration.inMilliseconds > 0 ? _currentPosition.inMilliseconds / _totalDuration.inMilliseconds : 0.0; // Free audio sources for demo purposes // Using royalty-free music from reliable sources final Map _demoAudioUrls = { // Peaceful, lido-appropriate tracks 'pop1': 'https://www.bensound.com/bensound-music/bensound-relaxing.mp3', 'pop2': 'https://www.bensound.com/bensound-music/bensound-sunny.mp3', 'pop3': 'https://www.bensound.com/bensound-music/bensound-jazzcomedy.mp3', 'pop4': 'https://www.bensound.com/bensound-music/bensound-acousticbreeze.mp3', '1': 'https://www.bensound.com/bensound-music/bensound-creativeminds.mp3', '2': 'https://www.bensound.com/bensound-music/bensound-happyrock.mp3', '3': 'https://www.bensound.com/bensound-music/bensound-ukulele.mp3', '4': 'https://www.bensound.com/bensound-music/bensound-summer.mp3', '5': 'https://www.bensound.com/bensound-music/bensound-happiness.mp3', }; void _initializePlayer() { // Listen to player state changes _audioPlayer.onPlayerStateChanged.listen((PlayerState state) { _isPlaying = state == PlayerState.playing; _isLoading = state == PlayerState.stopped && _currentTrack != null; notifyListeners(); }); // Listen to position changes _audioPlayer.onPositionChanged.listen((Duration position) { _currentPosition = position; notifyListeners(); }); // Listen to duration changes _audioPlayer.onDurationChanged.listen((Duration duration) { _totalDuration = duration; notifyListeners(); }); // Listen for track completion _audioPlayer.onPlayerComplete.listen((_) { _onTrackComplete(); }); } Future playTrack(SpotifyTrack track) async { try { _isLoading = true; _currentTrack = track; notifyListeners(); // Check if we have a demo URL for this track String? audioUrl = _demoAudioUrls[track.id]; if (audioUrl != null) { // Play the demo audio await _audioPlayer.play(UrlSource(audioUrl)); print('Playing audio for: ${track.name} by ${track.artistNames}'); } else { // For tracks without demo URLs, simulate playback print('Simulating playback for: ${track.name} by ${track.artistNames}'); _simulateTrackPlayback(track); } _isLoading = false; notifyListeners(); } catch (e) { print('Error playing track: $e'); _isLoading = false; // Fallback to simulation _simulateTrackPlayback(track); notifyListeners(); } } void _simulateTrackPlayback(SpotifyTrack track) { // Set simulated duration _totalDuration = Duration(milliseconds: track.durationMs); _currentPosition = Duration.zero; _isPlaying = true; // Simulate playback progress _startSimulatedProgress(); } void _startSimulatedProgress() { if (_isPlaying && _currentTrack != null) { Future.delayed(const Duration(seconds: 1), () { if (_isPlaying && _currentTrack != null) { _currentPosition = _currentPosition + const Duration(seconds: 1); if (_currentPosition >= _totalDuration) { _onTrackComplete(); } else { notifyListeners(); _startSimulatedProgress(); } } }); } } Future togglePlayPause() async { try { if (_isPlaying) { await _audioPlayer.pause(); } else { if (_currentTrack != null) { // Check if we have a real audio URL String? audioUrl = _demoAudioUrls[_currentTrack!.id]; if (audioUrl != null) { await _audioPlayer.resume(); } else { // Resume simulation _isPlaying = true; _startSimulatedProgress(); } } } notifyListeners(); } catch (e) { print('Error toggling play/pause: $e'); // Fallback to simulation toggle _isPlaying = !_isPlaying; if (_isPlaying) { _startSimulatedProgress(); } notifyListeners(); } } Future stop() async { try { await _audioPlayer.stop(); } catch (e) { print('Error stopping audio: $e'); } _isPlaying = false; _currentPosition = Duration.zero; _currentTrack = null; notifyListeners(); } Future seekTo(Duration position) async { try { // Check if we have a real audio URL if (_currentTrack != null && _demoAudioUrls.containsKey(_currentTrack!.id)) { await _audioPlayer.seek(position); } else { // Simulate seeking _currentPosition = position; notifyListeners(); } } catch (e) { print('Error seeking: $e'); // Fallback to simulation _currentPosition = position; notifyListeners(); } } void _onTrackComplete() { _isPlaying = false; _currentPosition = Duration.zero; notifyListeners(); // Notify that track is complete (for queue management) onTrackComplete?.call(); } // Callback for when a track completes Function()? onTrackComplete; @override void dispose() { _audioPlayer.dispose(); super.dispose(); } // Get formatted time strings String get currentPositionString => _formatDuration(_currentPosition); String get totalDurationString => _formatDuration(_totalDuration); String _formatDuration(Duration duration) { String twoDigits(int n) => n.toString().padLeft(2, '0'); String twoDigitMinutes = twoDigits(duration.inMinutes.remainder(60)); String twoDigitSeconds = twoDigits(duration.inSeconds.remainder(60)); return '$twoDigitMinutes:$twoDigitSeconds'; } // Add better demo audio URLs (using royalty-free sources) void addDemoAudioUrl(String trackId, String audioUrl) { _demoAudioUrls[trackId] = audioUrl; } // Add local asset support Future playAsset(SpotifyTrack track, String assetPath) async { try { _isLoading = true; _currentTrack = track; notifyListeners(); await _audioPlayer.play(AssetSource(assetPath)); print('Playing asset: $assetPath for ${track.name}'); _isLoading = false; notifyListeners(); } catch (e) { print('Error playing asset: $e'); _isLoading = false; _simulateTrackPlayback(track); notifyListeners(); } } }