Files
AudiOhm/frontend/lib/presentation/providers/music_provider.dart
T
root a89c7894cf Initial commit: AudiOhm - Alternative Spotify avec streaming YouTube
Backend:
- FastAPI avec PostgreSQL et Redis
- Authentification JWT complète
- API REST pour musique, playlists, recherche
- Streaming audio via yt-dlp
- SQLAlchemy 2.0 async

Frontend:
- Flutter avec thème néon cyberpunk
- State management Riverpod
- Layout adaptatif desktop/mobile
- Lecteur audio avec mini-player

Infrastructure:
- Docker Compose (PostgreSQL + Redis)
- Scripts d'installation automatisés
- Scripts de build pour exécutables

Fichiers ajoutés:
- BUILD_CLIENT_*.bat/sh: Scripts de compilation
- BUILD_CLIENT_README.md: Documentation compilation
- CHECK_FLUTTER.sh: Vérificateur d'environnement
- requirements.txt mis à jour pour Python 3.13
- Modèles SQLAlchemy corrigés (metadata -> extra_metadata)

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
2026-01-18 20:08:36 +00:00

225 lines
5.4 KiB
Dart

/// Music Provider - Player state management
library;
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:just_audio/just_audio.dart';
import '../../../domain/entities/track.dart';
/// Player state
class PlayerState {
final List<Track> queue;
final int currentIndex;
final bool isPlaying;
final Duration position;
final Duration duration;
final bool isLoading;
final String? errorMessage;
const PlayerState({
this.queue = const [],
this.currentIndex = -1,
this.isPlaying = false,
this.position = Duration.zero,
this.duration = Duration.zero,
this.isLoading = false,
this.errorMessage,
});
Track? get currentTrack =>
currentIndex >= 0 && currentIndex < queue.length
? queue[currentIndex]
: null;
PlayerState copyWith({
List<Track>? queue,
int? currentIndex,
bool? isPlaying,
Duration? position,
Duration? duration,
bool? isLoading,
String? errorMessage,
}) {
return PlayerState(
queue: queue ?? this.queue,
currentIndex: currentIndex ?? this.currentIndex,
isPlaying: isPlaying ?? this.isPlaying,
position: position ?? this.position,
duration: duration ?? this.duration,
isLoading: isLoading ?? this.isLoading,
errorMessage: errorMessage,
);
}
}
/// Player notifier
class PlayerNotifier extends StateNotifier<PlayerState> {
PlayerNotifier() : super(const PlayerState()) {
_player = AudioPlayer();
_init();
}
late final AudioPlayer _player;
void _init() {
_player.positionStream.listen((position) {
state = state.copyWith(position: position);
});
_player.durationStream.listen((duration) {
state = state.copyWith(duration: duration ?? Duration.zero);
});
_player.playerStateStream.listen((playerState) {
state = state.copyWith(
isPlaying: playerState.playing,
isLoading: playerState.processingState == ProcessingState.loading,
);
});
}
Future<void> loadTrack(Track track) async {
state = state.copyWith(isLoading: true);
try {
// Get stream URL from API
final streamUrl = track.audioUrl ?? '';
await _player.setUrl(streamUrl);
if (state.queue.isEmpty) {
state = state.copyWith(queue: [track], currentIndex: 0);
}
} catch (e) {
state = state.copyWith(
isLoading: false,
errorMessage: e.toString(),
);
}
}
Future<void> play() async {
if (state.currentTrack != null) {
await _player.play();
state = state.copyWith(isPlaying: true);
}
}
Future<void> pause() async {
await _player.pause();
state = state.copyWith(isPlaying: false);
}
Future<void> seek(Duration position) async {
await _player.seek(position);
}
Future<void> next() async {
if (state.currentIndex < state.queue.length - 1) {
final nextTrack = state.queue[state.currentIndex + 1];
await loadTrack(nextTrack);
state = state.copyWith(currentIndex: state.currentIndex + 1);
await play();
}
}
Future<void> previous() async {
if (state.currentIndex > 0) {
final previousTrack = state.queue[state.currentIndex - 1];
await loadTrack(previousTrack);
state = state.copyWith(currentIndex: state.currentIndex - 1);
await play();
}
}
void setQueue(List<Track> tracks, {int startIndex = 0}) {
state = state.copyWith(
queue: tracks,
currentIndex: startIndex,
);
}
void addToQueue(Track track) {
final newQueue = [...state.queue, track];
state = state.copyWith(queue: newQueue);
}
void removeFromQueue(int index) {
if (index >= 0 && index < state.queue.length) {
final newQueue = [...state.queue]..removeAt(index);
state = state.copyWith(queue: newQueue);
}
}
@override
void dispose() {
_player.dispose();
super.dispose();
}
}
/// Player provider
final playerProvider =
StateNotifierProvider<PlayerNotifier, PlayerState>((ref) {
return PlayerNotifier();
});
/// Current track provider
final currentTrackProvider = Provider<Track?>((ref) {
return ref.watch(playerProvider).currentTrack;
});
/// Queue view data class
class QueueViewData {
final Track? currentTrack;
final List<Track> queue;
final int currentIndex;
final bool isPlaying;
const QueueViewData({
required this.currentTrack,
required this.queue,
required this.currentIndex,
required this.isPlaying,
});
/// Get upcoming tracks (after current)
List<Track> get nextTracks {
if (currentIndex < 0 || currentIndex >= queue.length - 1) {
return [];
}
return queue.sublist(currentIndex + 1);
}
/// Get previously played tracks (before current)
List<Track> get previousTracks {
if (currentIndex <= 0) {
return [];
}
return queue.sublist(0, currentIndex);
}
/// Check if queue has tracks
bool get hasQueue => queue.isNotEmpty;
/// Check if there are upcoming tracks
bool get hasNextTracks => nextTracks.isNotEmpty;
/// Check if there are previous tracks
bool get hasPreviousTracks => previousTracks.isNotEmpty;
/// Get total queue count excluding current
int get queueCount => queue.length - 1;
}
/// Queue view provider
final queueProvider = Provider<QueueViewData>((ref) {
final playerState = ref.watch(playerProvider);
return QueueViewData(
currentTrack: playerState.currentTrack,
queue: playerState.queue,
currentIndex: playerState.currentIndex,
isPlaying: playerState.isPlaying,
);
});