diff --git a/lib/services/player_service.dart b/lib/services/player_service.dart index fab2c10..f759c1d 100644 --- a/lib/services/player_service.dart +++ b/lib/services/player_service.dart @@ -7,6 +7,9 @@ import '../models/song.dart'; import '../database/db_helper.dart'; import 'melo_logger.dart'; +/// Wiederhol-Modi für die Warteschlange. +enum Wiederholmodus { aus, titel, playlist } + class PlayerService extends ChangeNotifier { static final PlayerService _instanz = PlayerService._(); factory PlayerService() => _instanz; @@ -27,6 +30,9 @@ class PlayerService extends ChangeNotifier { /// Position des zuletzt gespielten Songs in [_shuffleReihenfolge] (-1 = noch nichts). int _shufflePos = -1; + // ─── Wiederholmodus (Repeat) ─── + Wiederholmodus _wiederholmodus = Wiederholmodus.aus; + AudioPlayer get _p { if (_player == null) { _player = AudioPlayer(); @@ -69,6 +75,16 @@ class PlayerService extends ChangeNotifier { 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. void _baueShuffleReihenfolge() { _shuffleReihenfolge.clear(); @@ -86,16 +102,6 @@ class PlayerService extends ChangeNotifier { _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 spiele(Song song, {int position = 0}) async { _aktuellerIndex = _warteschlange.indexWhere((s) => s.id == song.id); if (_aktuellerIndex < 0) { @@ -195,16 +201,49 @@ class PlayerService extends ChangeNotifier { Future naechstes() async { if (_player == null || aktuellerSong == null) return; - if (_zufallsmodus) { - final idx = _naechsterShuffleIndex(); - if (idx == null) return; - await spiele(_warteschlange[idx]); + if (_wiederholmodus == Wiederholmodus.titel) { + // Gleichen Song von vorn wiederholen + await _p.seek(Duration.zero); + await _p.play(); return; } - final neuerIndex = _aktuellerIndex + 1; - if (neuerIndex < _warteschlange.length) { - await spiele(_warteschlange[neuerIndex]); + final idx = naechsterIndex(); + if (idx == null) return; + 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 songs, {int startIndex = 0}) { diff --git a/lib/widgets/mini_player.dart b/lib/widgets/mini_player.dart index 9e66820..41e7081 100644 --- a/lib/widgets/mini_player.dart +++ b/lib/widgets/mini_player.dart @@ -107,6 +107,8 @@ class _MiniPlayerState extends State { ), // Shuffle _shuffleBtn(), + // Repeat (3 Zustände) + _repeatBtn(), // Steuerung _btn(Icons.skip_previous, _player.vorheriges), _playBtn(), @@ -142,6 +144,26 @@ class _MiniPlayerState extends State { ); } + /// 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}) { return Material( color: Colors.transparent,