v2.49.5 — Play-Next verschiebt statt zu duplizieren + Shuffle-Priorität
## Fix (Code-Review Finding 3, MED) - spieleAlsNaechstes: Song bereits in der Queue wird an Position nach dem aktuellen VERSCHOBEN (inkl. _aktuellerIndex-Anpassung), keine Kopie mehr - Shuffle: keine Neu-Mischung nach Play-Next — Song wird via _pinneInShuffleReihenfolge an die nächste Position gepinnt (Index- Korrektur für Verschiebungen, _shufflePos-Nachführung am Reihenende) - 3 Tests ergänzt: Verschieben statt Duplizieren, Song von vor dem aktuellen, Shuffle-Pin (kein Duplikat + naechsterIndex == gepinnter Song)
This commit is contained in:
@@ -277,15 +277,102 @@ class PlayerService extends ChangeNotifier {
|
|||||||
/// Index des aktuell spielenden Songs in der Warteschlange.
|
/// Index des aktuell spielenden Songs in der Warteschlange.
|
||||||
int get aktuellerIndex => _aktuellerIndex;
|
int get aktuellerIndex => _aktuellerIndex;
|
||||||
|
|
||||||
/// Fügt [song] direkt nach dem aktuellen Song ein.
|
/// Setzt [song] als nächstes auf die Wiedergabe — direkt nach dem aktuellen
|
||||||
|
/// Song. Ist der Song bereits in der Warteschlange, wird er dorthin VERSCHOBEN
|
||||||
|
/// statt dupliziert. Im Shuffle wird die Reihenfolge nicht neu gemischt:
|
||||||
|
/// der Song wird an die nächste Position gepinnt (Play-Next hat Priorität).
|
||||||
void spieleAlsNaechstes(Song song) {
|
void spieleAlsNaechstes(Song song) {
|
||||||
if (_aktuellerIndex < 0 || _warteschlange.isEmpty) {
|
if (_warteschlange.isEmpty) {
|
||||||
_warteschlange.add(song);
|
_warteschlange.add(song);
|
||||||
} else {
|
|
||||||
_warteschlange.insert(_aktuellerIndex + 1, song);
|
|
||||||
}
|
|
||||||
if (_zufallsmodus) _baueShuffleReihenfolge();
|
if (_zufallsmodus) _baueShuffleReihenfolge();
|
||||||
notifyListeners();
|
notifyListeners();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (_aktuellerIndex < 0) {
|
||||||
|
// Kein aktiver Song → ans Ende hängen
|
||||||
|
final vorhanden = _warteschlange.indexWhere((s) => s.id == song.id);
|
||||||
|
if (vorhanden < 0) _warteschlange.add(song);
|
||||||
|
if (_zufallsmodus) _baueShuffleReihenfolge();
|
||||||
|
notifyListeners();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
final ziel = _aktuellerIndex + 1;
|
||||||
|
final vorhandenerIndex = _warteschlange.indexWhere((s) => s.id == song.id);
|
||||||
|
var gepinnt = -1;
|
||||||
|
var alterIndex = -1;
|
||||||
|
var einfuegePosition = -1;
|
||||||
|
var queueGeaendert = false;
|
||||||
|
|
||||||
|
if (vorhandenerIndex >= 0 && vorhandenerIndex != _aktuellerIndex) {
|
||||||
|
// Bereits in der Queue → verschieben statt duplizieren
|
||||||
|
if (vorhandenerIndex != ziel) {
|
||||||
|
alterIndex = vorhandenerIndex;
|
||||||
|
final item = _warteschlange.removeAt(vorhandenerIndex);
|
||||||
|
einfuegePosition = vorhandenerIndex < ziel ? ziel - 1 : ziel;
|
||||||
|
_warteschlange.insert(einfuegePosition, item);
|
||||||
|
if (vorhandenerIndex < _aktuellerIndex) _aktuellerIndex -= 1;
|
||||||
|
gepinnt = einfuegePosition;
|
||||||
|
queueGeaendert = true;
|
||||||
|
} else {
|
||||||
|
gepinnt = vorhandenerIndex; // steht schon direkt nach dem aktuellen
|
||||||
|
}
|
||||||
|
} else if (vorhandenerIndex < 0) {
|
||||||
|
einfuegePosition = ziel;
|
||||||
|
_warteschlange.insert(ziel, song);
|
||||||
|
gepinnt = ziel;
|
||||||
|
queueGeaendert = true;
|
||||||
|
}
|
||||||
|
// vorhandenerIndex == _aktuellerIndex → Song läuft gerade, nichts zu tun
|
||||||
|
|
||||||
|
if (_zufallsmodus && gepinnt >= 0) {
|
||||||
|
_pinneInShuffleReihenfolge(
|
||||||
|
gepinnt: gepinnt,
|
||||||
|
alterIndex: alterIndex,
|
||||||
|
einfuegePosition: einfuegePosition,
|
||||||
|
queueGeaendert: queueGeaendert,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
notifyListeners();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Korrigiert [_shuffleReihenfolge] nach einer Queue-Änderung und pinnt
|
||||||
|
/// [gepinnt] (neuer Queue-Index des Songs) als nächstes.
|
||||||
|
void _pinneInShuffleReihenfolge({
|
||||||
|
required int gepinnt,
|
||||||
|
required int alterIndex,
|
||||||
|
required int einfuegePosition,
|
||||||
|
required bool queueGeaendert,
|
||||||
|
}) {
|
||||||
|
if (queueGeaendert) {
|
||||||
|
// 1) Entfernung spiegeln (alter Index war evtl. noch in der Reihenfolge)
|
||||||
|
final entferntPos = alterIndex >= 0
|
||||||
|
? _shuffleReihenfolge.indexOf(alterIndex)
|
||||||
|
: -1;
|
||||||
|
if (entferntPos >= 0) {
|
||||||
|
_shuffleReihenfolge.removeAt(entferntPos);
|
||||||
|
if (entferntPos < _shufflePos) _shufflePos -= 1;
|
||||||
|
for (var k = 0; k < _shuffleReihenfolge.length; k++) {
|
||||||
|
if (_shuffleReihenfolge[k] > alterIndex) {
|
||||||
|
_shuffleReihenfolge[k] -= 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// 2) Einfügung spiegeln (Indizes ab der Einfügeposition rücken nach rechts)
|
||||||
|
for (var k = 0; k < _shuffleReihenfolge.length; k++) {
|
||||||
|
if (_shuffleReihenfolge[k] >= einfuegePosition) {
|
||||||
|
_shuffleReihenfolge[k] += 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// 3) Song an die Position direkt nach dem aktuellen pinnten
|
||||||
|
_shuffleReihenfolge.remove(gepinnt);
|
||||||
|
final pinPos = _shufflePos + 1 > _shuffleReihenfolge.length
|
||||||
|
? _shuffleReihenfolge.length
|
||||||
|
: _shufflePos + 1;
|
||||||
|
_shuffleReihenfolge.insert(pinPos, gepinnt);
|
||||||
|
// _shufflePos so setzen, dass der gepinnte Song als nächstes gespielt wird
|
||||||
|
_shufflePos = pinPos - 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Hängt [song] ans Ende der Warteschlange an.
|
/// Hängt [song] ans Ende der Warteschlange an.
|
||||||
|
|||||||
@@ -51,6 +51,37 @@ void main() {
|
|||||||
expect(p.aktuellerIndex, -1);
|
expect(p.aktuellerIndex, -1);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test('spieleAlsNaechstes verschiebt vorhandenen Song statt zu duplizieren', () {
|
||||||
|
final p = PlayerService();
|
||||||
|
p.setWarteschlange([_song(1, 'A'), _song(2, 'B'), _song(3, 'C')]);
|
||||||
|
p.spieleAlsNaechstes(_song(3, 'C'));
|
||||||
|
expect(p.warteschlange.map((s) => s.id).toList(), [1, 3, 2]);
|
||||||
|
expect(p.warteschlange.length, 3);
|
||||||
|
// Wiederholter Aufruf erzeugt weiterhin keine Duplikate
|
||||||
|
p.spieleAlsNaechstes(_song(3, 'C'));
|
||||||
|
expect(p.warteschlange.length, 3);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('spieleAlsNaechstes verschiebt Song von vor dem aktuellen', () {
|
||||||
|
final p = PlayerService();
|
||||||
|
p.setWarteschlange([_song(1, 'A'), _song(2, 'B'), _song(3, 'C')], startIndex: 2);
|
||||||
|
p.spieleAlsNaechstes(_song(1, 'A'));
|
||||||
|
expect(p.warteschlange.map((s) => s.id).toList(), [2, 3, 1]);
|
||||||
|
expect(p.aktuellerIndex, 1); // aktueller Song (C) wandert auf Index 1
|
||||||
|
});
|
||||||
|
|
||||||
|
test('spieleAlsNaechstes im Shuffle: Song wird als nächstes gespielt', () {
|
||||||
|
final p = PlayerService();
|
||||||
|
p.setWarteschlange([_song(1, 'A'), _song(2, 'B'), _song(3, 'C'), _song(4, 'D')]);
|
||||||
|
p.setZufallsmodus(true);
|
||||||
|
p.spieleAlsNaechstes(_song(3, 'C'));
|
||||||
|
// Kein Duplikat: alle 4 Songs genau einmal
|
||||||
|
expect(p.warteschlange.map((s) => s.id).toSet(), {1, 2, 3, 4});
|
||||||
|
expect(p.warteschlange.length, 4);
|
||||||
|
// C (jetzt direkt nach dem aktuellen) wird als nächstes gespielt
|
||||||
|
expect(p.naechsterIndex(), 1);
|
||||||
|
});
|
||||||
|
|
||||||
test('amEndeHinzufuegen hängt ans Ende an', () {
|
test('amEndeHinzufuegen hängt ans Ende an', () {
|
||||||
final p = PlayerService();
|
final p = PlayerService();
|
||||||
p.setWarteschlange([_song(1, 'A')]);
|
p.setWarteschlange([_song(1, 'A')]);
|
||||||
|
|||||||
Reference in New Issue
Block a user