v2.48.1 — Repeat-Modus (Aus/Titel/Playlist)

## PlayerService
- Enum Wiederholmodus { aus, titel, playlist } + Getter/Setter
- naechstes(): titel → gleicher Song von vorn (seek 0 + play)
- naechsterIndex(): zentrale Berechnung des nächsten Index
  - playlist: nach letztem Song wieder von vorne (auch bei Shuffle: neu mischen)
  - aus: Ende der Warteschlange stoppt
- Auto-Next (ProcessingState.completed) läuft über naechstes() → Repeat wirkt auch automatisch

## MiniPlayer
- Repeat-Icon mit 3 Zuständen (repeat / repeat_one), rot wenn aktiv, Wechsel bei Tipp

Keine neuen Packages, flutter analyze 0 Issues, Tests 4/4
This commit is contained in:
Dustin
2026-08-04 16:24:26 +02:00
parent bb8ee56894
commit 42767a5646
2 changed files with 78 additions and 17 deletions
+56 -17
View File
@@ -7,6 +7,9 @@ import '../models/song.dart';
import '../database/db_helper.dart'; import '../database/db_helper.dart';
import 'melo_logger.dart'; import 'melo_logger.dart';
/// Wiederhol-Modi für die Warteschlange.
enum Wiederholmodus { aus, titel, playlist }
class PlayerService extends ChangeNotifier { class PlayerService extends ChangeNotifier {
static final PlayerService _instanz = PlayerService._(); static final PlayerService _instanz = PlayerService._();
factory PlayerService() => _instanz; factory PlayerService() => _instanz;
@@ -27,6 +30,9 @@ class PlayerService extends ChangeNotifier {
/// Position des zuletzt gespielten Songs in [_shuffleReihenfolge] (-1 = noch nichts). /// Position des zuletzt gespielten Songs in [_shuffleReihenfolge] (-1 = noch nichts).
int _shufflePos = -1; int _shufflePos = -1;
// ─── Wiederholmodus (Repeat) ───
Wiederholmodus _wiederholmodus = Wiederholmodus.aus;
AudioPlayer get _p { AudioPlayer get _p {
if (_player == null) { if (_player == null) {
_player = AudioPlayer(); _player = AudioPlayer();
@@ -69,6 +75,16 @@ class PlayerService extends ChangeNotifier {
notifyListeners(); notifyListeners();
} }
// ─── Wiederholmodus (Repeat) ───
Wiederholmodus get wiederholmodus => _wiederholmodus;
void setWiederholmodus(Wiederholmodus modus) {
if (_wiederholmodus == modus) return;
_wiederholmodus = modus;
notifyListeners();
}
/// Baut die Zufallsreihenfolge neu: alle Indizes außer dem aktuellen Song, gemischt. /// Baut die Zufallsreihenfolge neu: alle Indizes außer dem aktuellen Song, gemischt.
void _baueShuffleReihenfolge() { void _baueShuffleReihenfolge() {
_shuffleReihenfolge.clear(); _shuffleReihenfolge.clear();
@@ -86,16 +102,6 @@ class PlayerService extends ChangeNotifier {
_shuffleReihenfolge.addAll(indices); _shuffleReihenfolge.addAll(indices);
} }
/// Nächster Index in der Zufallsreihenfolge (null = alle durchgespielt).
int? _naechsterShuffleIndex() {
_shufflePos++;
if (_shufflePos < 0 || _shufflePos >= _shuffleReihenfolge.length) {
return null;
}
final idx = _shuffleReihenfolge[_shufflePos];
return (idx >= 0 && idx < _warteschlange.length) ? idx : null;
}
Future<void> spiele(Song song, {int position = 0}) async { Future<void> spiele(Song song, {int position = 0}) async {
_aktuellerIndex = _warteschlange.indexWhere((s) => s.id == song.id); _aktuellerIndex = _warteschlange.indexWhere((s) => s.id == song.id);
if (_aktuellerIndex < 0) { if (_aktuellerIndex < 0) {
@@ -195,16 +201,49 @@ class PlayerService extends ChangeNotifier {
Future<void> naechstes() async { Future<void> naechstes() async {
if (_player == null || aktuellerSong == null) return; if (_player == null || aktuellerSong == null) return;
if (_zufallsmodus) { if (_wiederholmodus == Wiederholmodus.titel) {
final idx = _naechsterShuffleIndex(); // Gleichen Song von vorn wiederholen
if (idx == null) return; await _p.seek(Duration.zero);
await spiele(_warteschlange[idx]); await _p.play();
return; return;
} }
final neuerIndex = _aktuellerIndex + 1; final idx = naechsterIndex();
if (neuerIndex < _warteschlange.length) { if (idx == null) return;
await spiele(_warteschlange[neuerIndex]); await spiele(_warteschlange[idx]);
} }
/// Berechnet den Index des nächsten Songs in der Warteschlange —
/// berücksichtigt Zufallsmodus und Wiederholmodus. Gibt `null` zurück,
/// wenn die Warteschlange zu Ende ist (Repeat aus).
int? naechsterIndex() {
if (_aktuellerIndex < 0 || _warteschlange.isEmpty) return null;
if (_wiederholmodus == Wiederholmodus.titel) return _aktuellerIndex;
if (_zufallsmodus) {
_shufflePos++;
if (_shufflePos >= _shuffleReihenfolge.length) {
if (_wiederholmodus == Wiederholmodus.playlist) {
// Alle durchgespielt → neu mischen und weiter
_baueShuffleReihenfolge();
_shufflePos++;
} else {
return null;
}
}
if (_shufflePos < 0 || _shufflePos >= _shuffleReihenfolge.length) {
return null;
}
final idx = _shuffleReihenfolge[_shufflePos];
return (idx >= 0 && idx < _warteschlange.length) ? idx : null;
}
var neuerIndex = _aktuellerIndex + 1;
if (neuerIndex >= _warteschlange.length) {
if (_wiederholmodus == Wiederholmodus.playlist) {
neuerIndex = 0;
} else {
return null;
}
}
return neuerIndex;
} }
void setWarteschlange(List<Song> songs, {int startIndex = 0}) { void setWarteschlange(List<Song> songs, {int startIndex = 0}) {
+22
View File
@@ -107,6 +107,8 @@ class _MiniPlayerState extends State<MiniPlayer> {
), ),
// Shuffle // Shuffle
_shuffleBtn(), _shuffleBtn(),
// Repeat (3 Zustände)
_repeatBtn(),
// Steuerung // Steuerung
_btn(Icons.skip_previous, _player.vorheriges), _btn(Icons.skip_previous, _player.vorheriges),
_playBtn(), _playBtn(),
@@ -142,6 +144,26 @@ class _MiniPlayerState extends State<MiniPlayer> {
); );
} }
/// Repeat-Icon — 3 Zustände (aus → Titel → Playlist), Wechsel bei Tipp
Widget _repeatBtn() {
final modus = _player.wiederholmodus;
final (icon, farbe) = switch (modus) {
Wiederholmodus.aus => (Icons.repeat, Colors.white),
Wiederholmodus.titel => (Icons.repeat_one, MeloTheme.rot),
Wiederholmodus.playlist => (Icons.repeat, MeloTheme.rot),
};
return _btn(icon, _naechsterWiederholmodus, farbe: farbe);
}
void _naechsterWiederholmodus() {
final modus = _player.wiederholmodus;
_player.setWiederholmodus(switch (modus) {
Wiederholmodus.aus => Wiederholmodus.titel,
Wiederholmodus.titel => Wiederholmodus.playlist,
Wiederholmodus.playlist => Wiederholmodus.aus,
});
}
Widget _btn(IconData icon, VoidCallback onTap, {Color farbe = Colors.white}) { Widget _btn(IconData icon, VoidCallback onTap, {Color farbe = Colors.white}) {
return Material( return Material(
color: Colors.transparent, color: Colors.transparent,