204 lines
5.3 KiB
Dart
204 lines
5.3 KiB
Dart
import 'package:flutter/foundation.dart';
|
|
import '../models/spotify_track.dart';
|
|
import '../services/spotify_service.dart';
|
|
|
|
class QueueItem {
|
|
final SpotifyTrack track;
|
|
int votes;
|
|
bool userVoted;
|
|
final DateTime addedAt;
|
|
|
|
QueueItem({
|
|
required this.track,
|
|
this.votes = 1,
|
|
this.userVoted = true,
|
|
DateTime? addedAt,
|
|
}) : addedAt = addedAt ?? DateTime.now();
|
|
|
|
Map<String, dynamic> toJson() => {
|
|
'id': track.id,
|
|
'title': track.name,
|
|
'artist': track.artistNames,
|
|
'votes': votes,
|
|
'userVoted': userVoted,
|
|
'duration': track.duration,
|
|
'imageUrl': track.imageUrl,
|
|
'addedAt': addedAt.toIso8601String(),
|
|
};
|
|
}
|
|
|
|
class MusicQueueService extends ChangeNotifier {
|
|
static final MusicQueueService _instance = MusicQueueService._internal();
|
|
factory MusicQueueService() => _instance;
|
|
MusicQueueService._internal();
|
|
|
|
final SpotifyService _spotifyService = SpotifyService();
|
|
|
|
// Current playing track
|
|
SpotifyTrack? _currentTrack;
|
|
bool _isPlaying = false;
|
|
double _progress = 0.0;
|
|
|
|
// Queue management
|
|
final List<QueueItem> _queue = [];
|
|
|
|
// Recently played
|
|
final List<SpotifyTrack> _recentlyPlayed = [];
|
|
|
|
// Getters
|
|
SpotifyTrack? get currentTrack => _currentTrack;
|
|
bool get isPlaying => _isPlaying;
|
|
double get progress => _progress;
|
|
List<QueueItem> get queue => List.unmodifiable(_queue);
|
|
List<SpotifyTrack> get recentlyPlayed => List.unmodifiable(_recentlyPlayed);
|
|
|
|
// Queue operations
|
|
void addToQueue(SpotifyTrack track) {
|
|
// Check if track is already in queue
|
|
final existingIndex = _queue.indexWhere((item) => item.track.id == track.id);
|
|
|
|
if (existingIndex != -1) {
|
|
// If track exists, upvote it
|
|
upvote(existingIndex);
|
|
} else {
|
|
// Add new track to queue
|
|
final queueItem = QueueItem(track: track);
|
|
_queue.add(queueItem);
|
|
_sortQueue();
|
|
notifyListeners();
|
|
print('Added "${track.name}" by ${track.artistNames} to queue');
|
|
}
|
|
}
|
|
|
|
void upvote(int index) {
|
|
if (index >= 0 && index < _queue.length) {
|
|
_queue[index].votes++;
|
|
if (!_queue[index].userVoted) {
|
|
_queue[index].userVoted = true;
|
|
}
|
|
_sortQueue();
|
|
notifyListeners();
|
|
}
|
|
}
|
|
|
|
void downvote(int index) {
|
|
if (index >= 0 && index < _queue.length) {
|
|
if (_queue[index].votes > 0) {
|
|
_queue[index].votes--;
|
|
}
|
|
_sortQueue();
|
|
notifyListeners();
|
|
}
|
|
}
|
|
|
|
void _sortQueue() {
|
|
_queue.sort((a, b) {
|
|
// First sort by votes (descending)
|
|
final voteComparison = b.votes.compareTo(a.votes);
|
|
if (voteComparison != 0) return voteComparison;
|
|
|
|
// If votes are equal, sort by time added (ascending - first come first serve)
|
|
return a.addedAt.compareTo(b.addedAt);
|
|
});
|
|
}
|
|
|
|
// Playback simulation
|
|
void playNext() {
|
|
if (_queue.isNotEmpty) {
|
|
final nextItem = _queue.removeAt(0);
|
|
_currentTrack = nextItem.track;
|
|
_isPlaying = true;
|
|
_progress = 0.0;
|
|
|
|
// Add to recently played
|
|
_recentlyPlayed.insert(0, nextItem.track);
|
|
if (_recentlyPlayed.length > 10) {
|
|
_recentlyPlayed.removeLast();
|
|
}
|
|
|
|
notifyListeners();
|
|
print('Now playing: ${_currentTrack!.name} by ${_currentTrack!.artistNames}');
|
|
|
|
// Simulate track progress
|
|
_simulatePlayback();
|
|
}
|
|
}
|
|
|
|
void togglePlayPause() {
|
|
_isPlaying = !_isPlaying;
|
|
notifyListeners();
|
|
}
|
|
|
|
void skipTrack() {
|
|
playNext();
|
|
}
|
|
|
|
void _simulatePlayback() {
|
|
if (_currentTrack == null) return;
|
|
|
|
// Simulate track progress over time
|
|
Future.delayed(const Duration(seconds: 1), () {
|
|
if (_isPlaying && _currentTrack != null) {
|
|
_progress += 1.0 / (_currentTrack!.durationMs / 1000);
|
|
if (_progress >= 1.0) {
|
|
// Track finished, play next
|
|
playNext();
|
|
} else {
|
|
notifyListeners();
|
|
_simulatePlayback();
|
|
}
|
|
}
|
|
});
|
|
}
|
|
|
|
// Initialize with some popular tracks
|
|
Future<void> initializeQueue() async {
|
|
if (_queue.isEmpty && _currentTrack == null) {
|
|
try {
|
|
final popularTracks = await _spotifyService.getPopularTracks();
|
|
for (final track in popularTracks.take(4)) {
|
|
final queueItem = QueueItem(
|
|
track: track,
|
|
votes: 10 - popularTracks.indexOf(track) * 2, // Decreasing votes
|
|
userVoted: false,
|
|
);
|
|
_queue.add(queueItem);
|
|
}
|
|
|
|
// Start playing the first track
|
|
if (_queue.isNotEmpty) {
|
|
playNext();
|
|
}
|
|
|
|
notifyListeners();
|
|
} catch (e) {
|
|
print('Error initializing queue: $e');
|
|
}
|
|
}
|
|
}
|
|
|
|
// Search functionality
|
|
Future<List<SpotifyTrack>> searchTracks(String query) async {
|
|
return await _spotifyService.searchTracks(query, limit: 20);
|
|
}
|
|
|
|
// Get queue as JSON for display
|
|
List<Map<String, dynamic>> get queueAsJson {
|
|
return _queue.map((item) => item.toJson()).toList();
|
|
}
|
|
|
|
// Get current track info for display
|
|
Map<String, dynamic>? get currentTrackInfo {
|
|
if (_currentTrack == null) return null;
|
|
|
|
return {
|
|
'title': _currentTrack!.name,
|
|
'artist': _currentTrack!.artistNames,
|
|
'album': _currentTrack!.album.name,
|
|
'imageUrl': _currentTrack!.imageUrl,
|
|
'duration': _currentTrack!.duration,
|
|
'progress': _progress,
|
|
'isPlaying': _isPlaying,
|
|
};
|
|
}
|
|
}
|